-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpacker.go
247 lines (220 loc) · 7.75 KB
/
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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
package rectpack
import (
"errors"
"fmt"
"slices"
)
// DefaultSize is the default width/height used as the maximum extent for packing rectangles.
//
// There is based off a maximum texture size for many modern GPUs. If this library is not being
// used for creating a texture atlas, then there is absolutely no significance about this number
// other than providing a sane starting point.
const DefaultSize = 4096
// Packer contains the state of a 2D rectangle packer.
type Packer struct {
// unpacked contains sizes that have not yet been packed or unable to be packed.
unpacked []Size
// algo is the algorithm implementation that performs the actual computation.
algo packAlgorithm
// sortFunc contains the function that will be used to determine comparison of sizes
// when sorting.
sortFunc SortFunc
// Padding defines the amount of empty space to place around rectangles. Values of 0 or less
// indicates that rectangles will be tightly packed.
//
// Default: 0
Padding int
// sortRev is flag indicating if reverse-ordering of rectangles during sorting should be
// enabled.
//
// Default: false
sortRev bool
// Online indicates if rectangles should be packed as they are inserted (online), or simply
// collected until Pack is called.
//
// There is a trade-off to online/offline packing.
//
// * Online packing is faster to pack due to a lack of sorting or comparing to
// other rectangles, but results in significantly less optimized results.
// * Offline packing can be significantly slower, but allows the algorithm to achieve its
// maximum potential by having all sizes known ahead of time and sorting for efficiency.
//
// Unless you are packing and using the results in real-time, it is recommended to use
// offline mode (default). For tasks such creating a texture atlas, spending the extra time
// to prepare the atlas in the most efficient manner is well worth the extra milliseconds
// of computation.
//
// Default: false
Online bool
}
// Size computes the size of the current packing. The returned value is the minimum size required
// to contain all packed rectangles.
func (p *Packer) Size() Size {
var size Size
for _, rect := range p.algo.Rects() {
size.Width = max(size.Width, rect.Right()+p.Padding)
size.Height = max(size.Height, rect.Bottom()+p.Padding)
}
return size
}
// Insert adds to rectangles to the packer.
//
// When online mode is enabled, the rectangle(s) are immediately packed. The return value will
// contain any values that could not be packed due to size limitations, or an empty slice upon
// success.
//
// When online mode is disabled, the rectangles(s) are simply staged to be packed with the
// next call to Pack. The return value will contain a slice of all rectangles that are currently
// staged.
func (p *Packer) Insert(sizes ...Size) []Size {
if p.Online {
return p.algo.Insert(p.Padding, sizes...)
}
p.unpacked = append(p.unpacked, sizes...)
return p.unpacked
}
// Insert adds to rectangles to the packer.
//
// When online mode is enabled, the rectangle(s) are immediately packed. The return value will
// contain any values that could not be packed due to size limitations, or an empty slice upon
// success.
//
// When online mode is disabled, the rectangles(s) are simply staged to be packed with the
// next call to Pack. The return value will contain a slice of all rectangles that are currently
// staged.
func (p *Packer) InsertSize(id, width, height int) bool {
result := p.Insert(NewSizeID(id, width, height))
if p.Online && len(result) != 0 {
return false
}
return true
}
// Sorter sets the comparer function used for pre-sorting sizes before packing. Depending on
// the algorithm and the input data, this can provide a significant improvement on efficiency.
//
// Default: SortArea
func (p *Packer) Sorter(compare SortFunc, reverse bool) {
p.sortFunc = compare
p.sortRev = reverse
}
// Rects returns a slice of rectangles that are currently packed.
//
// The backing memory is owned by the packer, and a copy should be made if modification or
// persistence is required.
func (p *Packer) Rects() []Rect {
return p.algo.Rects()
}
// Unpacked returns a slice of rectangles that are currently staged to be packed.
//
// The backing memory is owned by the packer, and a copy should be made if modification or
// persistence is required.
func (p *Packer) Unpacked() []Size {
return p.unpacked
}
// Used computes the ratio of used surface area to the available area, in the range of
// 0.0 and 1.0.
//
// When current is set to true, the ratio will reflect the ratio of used surface area relative
// to the current size required by the packer, otherwise it is the ratio of the maximum
// possible area.
func (p *Packer) Used(current bool) float64 {
if current {
size := p.Size()
return float64(p.algo.UsedArea()) / float64(size.Width * size.Height)
}
return p.algo.Used()
}
// Map creates and returns a map where each key is an ID, and the value is the rectangle it
// pertains to.
func (p *Packer) Map() map[int]Rect {
rects := p.algo.Rects()
mapping := make(map[int]Rect, len(rects))
for _, rect := range rects {
mapping[rect.ID] = rect
}
return mapping
}
// Clear resets the internal state of the packer without changing its current configuration. All
// currently packed and pending rectangles are removed.
func (p *Packer) Clear() {
size := p.algo.MaxSize()
p.algo.Reset(size.Width, size.Height)
p.unpacked = p.unpacked[:0]
}
// Pack will sort and pack all rectangles that are currently staged.
//
// The return value indicates if all staged rectangles were successfully packed. When false,
// Unpacked can be used to retrieve the sizes that failed.
func (p *Packer) Pack() bool {
if len(p.unpacked) == 0 {
return true
}
if p.sortFunc != nil {
if p.sortRev {
slices.SortFunc(p.unpacked, func(a, b Size) int {
return p.sortFunc(b, a)
})
} else {
slices.SortFunc(p.unpacked, p.sortFunc)
}
} else if p.sortRev {
slices.Reverse(p.unpacked)
}
failed := p.algo.Insert(p.Padding, p.unpacked...)
if len(failed) == 0 {
p.unpacked = p.unpacked[:0]
return true
}
p.unpacked = failed
return false
}
// RepackAll clears the internal packed rectangles, and repacks them all with one operation. This
// can be useful to optimize the packing when/if it was previously performed in multiple pack
// operations, or to reflect settings for the packer that have been modified.
func (p *Packer) RepackAll() bool {
rects := p.algo.Rects()
for _, rect := range rects {
p.unpacked = append(p.unpacked, rect.Size)
}
size := p.Size()
p.algo.Reset(size.Width, size.Height)
return p.Pack()
}
// AllowFlip indicates if rectangles can be flipped/rotated to provide better placement.
//
// Default: false
func (p *Packer) AllowFlip(enabled bool) {
p.algo.AllowFlip(enabled)
}
// NewPacker initializes a new Packer using the specified maximum size and heustistics for
// packing rectangles.
//
// A width/height less than 1 will cause a panic,
func NewPacker(maxWidth, maxHeight int, heuristic Heuristic) (*Packer, error) {
if maxWidth <= 0 || maxHeight <= 0 {
return nil, fmt.Errorf("width and height must be greater than 0 (given %vx%x)", maxWidth, maxHeight)
}
p := &Packer{
Online: false,
sortFunc: SortArea,
sortRev: false,
}
switch heuristic & typeMask {
case MaxRects:
p.algo = newMaxRects(maxWidth, maxHeight, heuristic)
case Skyline:
p.algo = newSkyline(maxWidth, maxHeight, heuristic)
case Guillotine:
p.algo = newGuillotine(maxWidth, maxHeight, heuristic)
default:
return nil, errors.New("heuristics specify an invalid argorithm")
}
return p, nil
}
// NewDefaultPacker initializes a new Packer with sensible default settings suitable for
// general-purpose rectangle packing.
func NewDefaultPacker() *Packer {
packer, _ := NewPacker(DefaultSize, DefaultSize, SkylineBLF)
return packer
}
// vim: ts=4