packing.go (6748B)
1 // Copyright 2018 The Ebiten Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package packing offers a packing algorithm in 2D space. 16 package packing 17 18 import ( 19 "errors" 20 ) 21 22 const ( 23 minSize = 1 24 ) 25 26 type Page struct { 27 root *Node 28 size int 29 maxSize int 30 31 rollbackExtension func() 32 } 33 34 func NewPage(initSize int, maxSize int) *Page { 35 return &Page{ 36 size: initSize, 37 maxSize: maxSize, 38 } 39 } 40 41 func (p *Page) IsEmpty() bool { 42 if p.root == nil { 43 return true 44 } 45 return !p.root.used && p.root.child0 == nil && p.root.child1 == nil 46 } 47 48 type Node struct { 49 x int 50 y int 51 width int 52 height int 53 used bool 54 55 parent *Node 56 child0 *Node 57 child1 *Node 58 } 59 60 func (n *Node) canFree() bool { 61 if n.used { 62 return false 63 } 64 if n.child0 == nil && n.child1 == nil { 65 return true 66 } 67 return n.child0.canFree() && n.child1.canFree() 68 } 69 70 func (n *Node) Region() (x, y, width, height int) { 71 return n.x, n.y, n.width, n.height 72 } 73 74 // square returns a float value indicating how much the given rectangle is close to a square. 75 // If the given rectangle is square, this return 1 (maximum value). 76 // Otherwise, this returns a value in [0, 1). 77 func square(width, height int) float64 { 78 if width == 0 && height == 0 { 79 return 0 80 } 81 if width <= height { 82 return float64(width) / float64(height) 83 } 84 return float64(height) / float64(width) 85 } 86 87 func (p *Page) alloc(n *Node, width, height int) *Node { 88 if n.width < width || n.height < height { 89 return nil 90 } 91 if n.used { 92 return nil 93 } 94 if n.child0 == nil && n.child1 == nil { 95 if n.width == width && n.height == height { 96 n.used = true 97 return n 98 } 99 if square(n.width-width, n.height) >= square(n.width, n.height-height) { 100 // Split vertically 101 n.child0 = &Node{ 102 x: n.x, 103 y: n.y, 104 width: width, 105 height: n.height, 106 parent: n, 107 } 108 n.child1 = &Node{ 109 x: n.x + width, 110 y: n.y, 111 width: n.width - width, 112 height: n.height, 113 parent: n, 114 } 115 } else { 116 // Split holizontally 117 n.child0 = &Node{ 118 x: n.x, 119 y: n.y, 120 width: n.width, 121 height: height, 122 parent: n, 123 } 124 n.child1 = &Node{ 125 x: n.x, 126 y: n.y + height, 127 width: n.width, 128 height: n.height - height, 129 parent: n, 130 } 131 } 132 return p.alloc(n.child0, width, height) 133 } 134 if n.child0 == nil || n.child1 == nil { 135 panic("packing: both two children must not be nil at alloc") 136 } 137 if node := p.alloc(n.child0, width, height); node != nil { 138 return node 139 } 140 if node := p.alloc(n.child1, width, height); node != nil { 141 return node 142 } 143 return nil 144 } 145 146 func (p *Page) Size() int { 147 return p.size 148 } 149 150 func (p *Page) SetMaxSize(size int) { 151 if p.maxSize > size { 152 panic("packing: maxSize cannot be decreased") 153 } 154 p.maxSize = size 155 } 156 157 func (p *Page) Alloc(width, height int) *Node { 158 if width <= 0 || height <= 0 { 159 panic("packing: width and height must > 0") 160 } 161 if p.root == nil { 162 p.root = &Node{ 163 width: p.size, 164 height: p.size, 165 } 166 } 167 if width < minSize { 168 width = minSize 169 } 170 if height < minSize { 171 height = minSize 172 } 173 n := p.alloc(p.root, width, height) 174 return n 175 } 176 177 func (p *Page) Free(node *Node) { 178 if node.child0 != nil || node.child1 != nil { 179 panic("packing: can't free the node including children") 180 } 181 node.used = false 182 if node.parent == nil { 183 return 184 } 185 if node.parent.child0 == nil || node.parent.child1 == nil { 186 panic("packing: both two children must not be nil at Free: double free happened?") 187 } 188 if node.parent.child0.canFree() && node.parent.child1.canFree() { 189 node.parent.child0 = nil 190 node.parent.child1 = nil 191 p.Free(node.parent) 192 } 193 } 194 195 func walk(n *Node, f func(n *Node) error) error { 196 if err := f(n); err != nil { 197 return err 198 } 199 if n.child0 != nil { 200 if err := walk(n.child0, f); err != nil { 201 return err 202 } 203 } 204 if n.child1 != nil { 205 if err := walk(n.child1, f); err != nil { 206 return err 207 } 208 } 209 return nil 210 } 211 212 func (p *Page) Extend(count int) bool { 213 if p.rollbackExtension != nil { 214 panic("packing: Extend cannot be called without rolling back or committing") 215 } 216 217 if p.size >= p.maxSize { 218 return false 219 } 220 221 newSize := p.size 222 for i := 0; i < count; i++ { 223 newSize *= 2 224 } 225 226 if newSize > p.maxSize { 227 return false 228 } 229 230 edgeNodes := []*Node{} 231 abort := errors.New("abort") 232 aborted := false 233 if p.root != nil { 234 _ = walk(p.root, func(n *Node) error { 235 if n.x+n.width < p.size && n.y+n.height < p.size { 236 return nil 237 } 238 if n.used { 239 aborted = true 240 return abort 241 } 242 edgeNodes = append(edgeNodes, n) 243 return nil 244 }) 245 } 246 247 if aborted { 248 origRoot := *p.root 249 250 leftUpper := p.root 251 leftLower := &Node{ 252 x: 0, 253 y: p.size, 254 width: p.size, 255 height: newSize - p.size, 256 } 257 left := &Node{ 258 x: 0, 259 y: 0, 260 width: p.size, 261 height: p.size, 262 child0: leftUpper, 263 child1: leftLower, 264 } 265 leftUpper.parent = left 266 leftLower.parent = left 267 268 right := &Node{ 269 x: p.size, 270 y: 0, 271 width: newSize - p.size, 272 height: newSize, 273 } 274 p.root = &Node{ 275 x: 0, 276 y: 0, 277 width: newSize, 278 height: newSize, 279 child0: left, 280 child1: right, 281 } 282 left.parent = p.root 283 right.parent = p.root 284 285 origSize := p.size 286 p.rollbackExtension = func() { 287 p.size = origSize 288 p.root = &origRoot 289 } 290 } else { 291 origSize := p.size 292 origWidths := map[*Node]int{} 293 origHeights := map[*Node]int{} 294 295 for _, n := range edgeNodes { 296 if n.x+n.width == p.size { 297 origWidths[n] = n.width 298 n.width += newSize - p.size 299 } 300 if n.y+n.height == p.size { 301 origHeights[n] = n.height 302 n.height += newSize - p.size 303 } 304 } 305 306 p.rollbackExtension = func() { 307 p.size = origSize 308 for n, w := range origWidths { 309 n.width = w 310 } 311 for n, h := range origHeights { 312 n.height = h 313 } 314 } 315 } 316 317 p.size = newSize 318 319 return true 320 } 321 322 // RollbackExtension rollbacks Extend call once. 323 func (p *Page) RollbackExtension() { 324 if p.rollbackExtension == nil { 325 panic("packing: RollbackExtension cannot be called without Extend") 326 } 327 p.rollbackExtension() 328 p.rollbackExtension = nil 329 } 330 331 // CommitExtension commits Extend call. 332 func (p *Page) CommitExtension() { 333 if p.rollbackExtension == nil { 334 panic("packing: RollbackExtension cannot be called without Extend") 335 } 336 p.rollbackExtension = nil 337 }