2018-02-25 12:16:27 +01:00
|
|
|
// Copyright 2018 The Ebiten Authors
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
2018-03-05 16:38:56 +01:00
|
|
|
// Package packing offers a packing algorithm in 2D space.
|
|
|
|
package packing
|
2018-02-25 12:16:27 +01:00
|
|
|
|
2018-03-10 17:13:53 +01:00
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
)
|
|
|
|
|
2018-02-25 12:16:27 +01:00
|
|
|
const (
|
2018-03-09 08:02:57 +01:00
|
|
|
minSize = 1
|
2018-02-25 12:16:27 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
type Page struct {
|
2018-03-10 12:53:20 +01:00
|
|
|
root *Node
|
2018-03-10 17:13:53 +01:00
|
|
|
size int
|
2018-03-10 12:53:20 +01:00
|
|
|
maxSize int
|
2019-11-17 16:23:10 +01:00
|
|
|
|
|
|
|
rollbackExtension func()
|
2018-03-10 12:53:20 +01:00
|
|
|
}
|
|
|
|
|
2018-03-10 17:13:53 +01:00
|
|
|
func NewPage(initSize int, maxSize int) *Page {
|
2018-03-10 12:53:20 +01:00
|
|
|
return &Page{
|
2018-03-10 17:13:53 +01:00
|
|
|
size: initSize,
|
2018-03-10 12:53:20 +01:00
|
|
|
maxSize: maxSize,
|
|
|
|
}
|
2018-02-25 12:16:27 +01:00
|
|
|
}
|
|
|
|
|
2018-03-03 10:51:52 +01:00
|
|
|
func (p *Page) IsEmpty() bool {
|
|
|
|
if p.root == nil {
|
|
|
|
return true
|
|
|
|
}
|
2018-03-10 16:28:30 +01:00
|
|
|
return !p.root.used && p.root.child0 == nil && p.root.child1 == nil
|
2018-03-03 10:51:52 +01:00
|
|
|
}
|
|
|
|
|
2018-02-25 12:16:27 +01:00
|
|
|
type Node struct {
|
|
|
|
x int
|
|
|
|
y int
|
|
|
|
width int
|
|
|
|
height int
|
2018-03-18 18:28:17 +01:00
|
|
|
used bool
|
2018-03-19 18:15:49 +01:00
|
|
|
|
2018-02-25 12:16:27 +01:00
|
|
|
parent *Node
|
|
|
|
child0 *Node
|
|
|
|
child1 *Node
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *Node) canFree() bool {
|
|
|
|
if n.used {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if n.child0 == nil && n.child1 == nil {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return n.child0.canFree() && n.child1.canFree()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n *Node) Region() (x, y, width, height int) {
|
|
|
|
return n.x, n.y, n.width, n.height
|
|
|
|
}
|
|
|
|
|
2018-02-26 03:20:20 +01:00
|
|
|
// square returns a float value indicating how much the given rectangle is close to a square.
|
|
|
|
// If the given rectangle is square, this return 1 (maximum value).
|
|
|
|
// Otherwise, this returns a value in [0, 1).
|
|
|
|
func square(width, height int) float64 {
|
|
|
|
if width == 0 && height == 0 {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
if width <= height {
|
|
|
|
return float64(width) / float64(height)
|
|
|
|
}
|
|
|
|
return float64(height) / float64(width)
|
|
|
|
}
|
|
|
|
|
2022-11-11 13:15:28 +01:00
|
|
|
func (p *Page) canAlloc(n *Node, width, height int) bool {
|
|
|
|
if p.root == nil {
|
|
|
|
return p.size >= width && p.size >= height
|
|
|
|
}
|
|
|
|
return canAlloc(p.root, width, height)
|
|
|
|
}
|
|
|
|
|
|
|
|
func canAlloc(n *Node, width, height int) bool {
|
|
|
|
if n.width < width || n.height < height {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if n.used {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if n.child0 == nil && n.child1 == nil {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if canAlloc(n.child0, width, height) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if canAlloc(n.child1, width, height) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func alloc(n *Node, width, height int) *Node {
|
2018-02-25 12:16:27 +01:00
|
|
|
if n.width < width || n.height < height {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if n.used {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if n.child0 == nil && n.child1 == nil {
|
|
|
|
if n.width == width && n.height == height {
|
|
|
|
n.used = true
|
|
|
|
return n
|
|
|
|
}
|
2018-02-26 03:20:20 +01:00
|
|
|
if square(n.width-width, n.height) >= square(n.width, n.height-height) {
|
2018-02-25 12:16:27 +01:00
|
|
|
// Split vertically
|
|
|
|
n.child0 = &Node{
|
|
|
|
x: n.x,
|
|
|
|
y: n.y,
|
|
|
|
width: width,
|
|
|
|
height: n.height,
|
|
|
|
parent: n,
|
|
|
|
}
|
|
|
|
n.child1 = &Node{
|
|
|
|
x: n.x + width,
|
|
|
|
y: n.y,
|
|
|
|
width: n.width - width,
|
|
|
|
height: n.height,
|
|
|
|
parent: n,
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Split holizontally
|
|
|
|
n.child0 = &Node{
|
|
|
|
x: n.x,
|
|
|
|
y: n.y,
|
|
|
|
width: n.width,
|
|
|
|
height: height,
|
|
|
|
parent: n,
|
|
|
|
}
|
|
|
|
n.child1 = &Node{
|
|
|
|
x: n.x,
|
|
|
|
y: n.y + height,
|
|
|
|
width: n.width,
|
|
|
|
height: n.height - height,
|
|
|
|
parent: n,
|
|
|
|
}
|
|
|
|
}
|
2022-11-11 13:15:28 +01:00
|
|
|
return alloc(n.child0, width, height)
|
2018-02-25 12:16:27 +01:00
|
|
|
}
|
|
|
|
if n.child0 == nil || n.child1 == nil {
|
2019-02-07 09:19:24 +01:00
|
|
|
panic("packing: both two children must not be nil at alloc")
|
2018-02-25 12:16:27 +01:00
|
|
|
}
|
2022-11-11 13:15:28 +01:00
|
|
|
if node := alloc(n.child0, width, height); node != nil {
|
2018-02-25 12:16:27 +01:00
|
|
|
return node
|
|
|
|
}
|
2022-11-11 13:15:28 +01:00
|
|
|
if node := alloc(n.child1, width, height); node != nil {
|
2018-02-25 12:16:27 +01:00
|
|
|
return node
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-11-11 13:15:28 +01:00
|
|
|
func (p *Page) Size() (int, int) {
|
|
|
|
return p.size, p.size
|
2018-03-10 17:13:53 +01:00
|
|
|
}
|
|
|
|
|
2019-06-22 13:17:52 +02:00
|
|
|
func (p *Page) SetMaxSize(size int) {
|
|
|
|
if p.maxSize > size {
|
|
|
|
panic("packing: maxSize cannot be decreased")
|
|
|
|
}
|
|
|
|
p.maxSize = size
|
|
|
|
}
|
|
|
|
|
2018-02-25 12:16:27 +01:00
|
|
|
func (p *Page) Alloc(width, height int) *Node {
|
|
|
|
if width <= 0 || height <= 0 {
|
2018-07-21 22:29:02 +02:00
|
|
|
panic("packing: width and height must > 0")
|
2018-02-25 12:16:27 +01:00
|
|
|
}
|
2022-11-11 13:15:28 +01:00
|
|
|
|
|
|
|
if !p.extendFor(width, height) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-02-25 12:16:27 +01:00
|
|
|
if p.root == nil {
|
|
|
|
p.root = &Node{
|
2018-03-10 17:13:53 +01:00
|
|
|
width: p.size,
|
|
|
|
height: p.size,
|
2018-02-25 12:16:27 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if width < minSize {
|
|
|
|
width = minSize
|
|
|
|
}
|
|
|
|
if height < minSize {
|
|
|
|
height = minSize
|
|
|
|
}
|
2022-11-11 13:15:28 +01:00
|
|
|
return alloc(p.root, width, height)
|
2018-02-25 12:16:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Page) Free(node *Node) {
|
|
|
|
if node.child0 != nil || node.child1 != nil {
|
2018-07-21 22:29:02 +02:00
|
|
|
panic("packing: can't free the node including children")
|
2018-02-25 12:16:27 +01:00
|
|
|
}
|
|
|
|
node.used = false
|
|
|
|
if node.parent == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if node.parent.child0 == nil || node.parent.child1 == nil {
|
2019-02-07 09:19:24 +01:00
|
|
|
panic("packing: both two children must not be nil at Free: double free happened?")
|
2018-02-25 12:16:27 +01:00
|
|
|
}
|
|
|
|
if node.parent.child0.canFree() && node.parent.child1.canFree() {
|
|
|
|
node.parent.child0 = nil
|
|
|
|
node.parent.child1 = nil
|
2018-03-10 16:28:30 +01:00
|
|
|
p.Free(node.parent)
|
2018-02-25 12:16:27 +01:00
|
|
|
}
|
|
|
|
}
|
2018-03-10 17:13:53 +01:00
|
|
|
|
|
|
|
func walk(n *Node, f func(n *Node) error) error {
|
|
|
|
if err := f(n); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if n.child0 != nil {
|
|
|
|
if err := walk(n.child0, f); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if n.child1 != nil {
|
|
|
|
if err := walk(n.child1, f); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-11-11 13:15:28 +01:00
|
|
|
func (p *Page) extendFor(width, height int) bool {
|
|
|
|
if p.canAlloc(p.root, width, height) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
nExtended := 1
|
|
|
|
for {
|
|
|
|
if !p.extend(nExtended) {
|
|
|
|
// The page can't be extended any more. Return as failure.
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
nExtended++
|
|
|
|
if p.canAlloc(p.root, width, height) {
|
|
|
|
p.rollbackExtension = nil
|
|
|
|
break
|
|
|
|
}
|
|
|
|
p.rollbackExtension()
|
|
|
|
p.rollbackExtension = nil
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Page) extend(count int) bool {
|
2019-11-17 16:23:10 +01:00
|
|
|
if p.rollbackExtension != nil {
|
2020-01-01 15:59:56 +01:00
|
|
|
panic("packing: Extend cannot be called without rolling back or committing")
|
2019-11-17 16:23:10 +01:00
|
|
|
}
|
|
|
|
|
2018-03-10 17:13:53 +01:00
|
|
|
if p.size >= p.maxSize {
|
|
|
|
return false
|
|
|
|
}
|
2020-12-31 09:33:24 +01:00
|
|
|
|
2019-11-17 15:21:50 +01:00
|
|
|
newSize := p.size
|
|
|
|
for i := 0; i < count; i++ {
|
|
|
|
newSize *= 2
|
|
|
|
}
|
2020-12-31 09:33:24 +01:00
|
|
|
|
|
|
|
if newSize > p.maxSize {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2018-03-10 17:13:53 +01:00
|
|
|
edgeNodes := []*Node{}
|
|
|
|
abort := errors.New("abort")
|
|
|
|
aborted := false
|
2020-12-31 09:33:24 +01:00
|
|
|
if p.root != nil {
|
|
|
|
_ = walk(p.root, func(n *Node) error {
|
|
|
|
if n.x+n.width < p.size && n.y+n.height < p.size {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if n.used {
|
|
|
|
aborted = true
|
|
|
|
return abort
|
|
|
|
}
|
|
|
|
edgeNodes = append(edgeNodes, n)
|
2018-03-10 17:13:53 +01:00
|
|
|
return nil
|
2020-12-31 09:33:24 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2018-03-10 17:13:53 +01:00
|
|
|
if aborted {
|
2019-11-17 16:23:10 +01:00
|
|
|
origRoot := *p.root
|
|
|
|
|
2018-03-10 17:13:53 +01:00
|
|
|
leftUpper := p.root
|
|
|
|
leftLower := &Node{
|
|
|
|
x: 0,
|
|
|
|
y: p.size,
|
|
|
|
width: p.size,
|
|
|
|
height: newSize - p.size,
|
|
|
|
}
|
|
|
|
left := &Node{
|
|
|
|
x: 0,
|
|
|
|
y: 0,
|
|
|
|
width: p.size,
|
|
|
|
height: p.size,
|
|
|
|
child0: leftUpper,
|
|
|
|
child1: leftLower,
|
|
|
|
}
|
|
|
|
leftUpper.parent = left
|
|
|
|
leftLower.parent = left
|
|
|
|
|
|
|
|
right := &Node{
|
|
|
|
x: p.size,
|
|
|
|
y: 0,
|
|
|
|
width: newSize - p.size,
|
|
|
|
height: newSize,
|
|
|
|
}
|
|
|
|
p.root = &Node{
|
|
|
|
x: 0,
|
|
|
|
y: 0,
|
|
|
|
width: newSize,
|
|
|
|
height: newSize,
|
|
|
|
child0: left,
|
|
|
|
child1: right,
|
|
|
|
}
|
|
|
|
left.parent = p.root
|
|
|
|
right.parent = p.root
|
2019-11-17 16:23:10 +01:00
|
|
|
|
|
|
|
origSize := p.size
|
|
|
|
p.rollbackExtension = func() {
|
|
|
|
p.size = origSize
|
|
|
|
p.root = &origRoot
|
|
|
|
}
|
2018-03-10 17:13:53 +01:00
|
|
|
} else {
|
2019-11-17 16:23:10 +01:00
|
|
|
origSize := p.size
|
|
|
|
origWidths := map[*Node]int{}
|
|
|
|
origHeights := map[*Node]int{}
|
|
|
|
|
2018-03-10 17:13:53 +01:00
|
|
|
for _, n := range edgeNodes {
|
|
|
|
if n.x+n.width == p.size {
|
2019-11-17 16:23:10 +01:00
|
|
|
origWidths[n] = n.width
|
2018-03-10 17:13:53 +01:00
|
|
|
n.width += newSize - p.size
|
|
|
|
}
|
|
|
|
if n.y+n.height == p.size {
|
2019-11-17 16:23:10 +01:00
|
|
|
origHeights[n] = n.height
|
2018-03-10 17:13:53 +01:00
|
|
|
n.height += newSize - p.size
|
|
|
|
}
|
|
|
|
}
|
2019-11-17 16:23:10 +01:00
|
|
|
|
|
|
|
p.rollbackExtension = func() {
|
|
|
|
p.size = origSize
|
|
|
|
for n, w := range origWidths {
|
|
|
|
n.width = w
|
|
|
|
}
|
|
|
|
for n, h := range origHeights {
|
|
|
|
n.height = h
|
|
|
|
}
|
|
|
|
}
|
2018-03-10 17:13:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
p.size = newSize
|
2020-12-31 09:33:24 +01:00
|
|
|
|
2018-03-10 17:13:53 +01:00
|
|
|
return true
|
|
|
|
}
|