zorldo

Goofing around with Ebiten
git clone git://bsandro.tech/zorldo
Log | Files | Refs | README

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 }