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"
2022-11-11 14:08:29 +01:00
"fmt"
2018-03-10 17:13:53 +01:00
)
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
2022-11-11 14:08:29 +01:00
width int
height int
2018-03-10 12:53:20 +01:00
maxSize int
}
2023-01-02 11:07:30 +01:00
func isPositivePowerOf2 ( x int ) bool {
if x <= 0 {
return false
}
for x > 1 {
if x / 2 * 2 != x {
return false
}
x /= 2
}
return true
}
2023-02-22 16:44:56 +01:00
func NewPage ( initWidth , initHeight int , maxSize int ) * Page {
if ! isPositivePowerOf2 ( initWidth ) {
panic ( fmt . Sprintf ( "packing: initWidth must be a positive power of 2 but %d" , initWidth ) )
}
if ! isPositivePowerOf2 ( initHeight ) {
panic ( fmt . Sprintf ( "packing: initHeight must be a positive power of 2 but %d" , initHeight ) )
2023-01-02 11:07:30 +01:00
}
if ! isPositivePowerOf2 ( maxSize ) {
panic ( fmt . Sprintf ( "packing: maxSize must be a positive power of 2 but %d" , maxSize ) )
}
2018-03-10 12:53:20 +01:00
return & Page {
2023-02-22 16:44:56 +01:00
width : initWidth ,
height : initHeight ,
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 {
2022-11-11 14:08:29 +01:00
return p . width >= width && p . height >= height
2022-11-11 13:15:28 +01:00
}
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 {
2023-01-28 11:06:38 +01:00
// Split horizontally
2018-02-25 12:16:27 +01:00
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 ) {
2022-11-11 14:08:29 +01:00
return p . width , p . height
2018-03-10 17:13:53 +01:00
}
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 {
2022-11-11 14:08:29 +01:00
width : p . width ,
height : p . height ,
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
}
2022-11-11 14:08:29 +01:00
if p . width >= p . maxSize && p . height >= p . maxSize {
return false
2022-11-11 13:15:28 +01:00
}
2022-11-11 14:08:29 +01:00
// (1, 0), (0, 1), (2, 0), (1, 1), (0, 2), (3, 0), (2, 1), (1, 2), (0, 3), ...
for i := 1 ; ; i ++ {
for j := 0 ; j <= i ; j ++ {
newWidth := p . width
for k := 0 ; k < i - j ; k ++ {
newWidth *= 2
}
newHeight := p . height
for k := 0 ; k < j ; k ++ {
newHeight *= 2
}
2019-11-17 16:23:10 +01:00
2022-11-11 14:08:29 +01:00
if newWidth > p . maxSize || newHeight > p . maxSize {
if newWidth > p . maxSize && newHeight > p . maxSize {
2023-01-02 11:07:30 +01:00
panic ( fmt . Sprintf ( "packing: too big extension: allocating size: (%d, %d), current size: (%d, %d), new size: (%d, %d), (i, j): (%d, %d), max size: %d" , width , height , p . width , p . height , newWidth , newHeight , i , j , p . maxSize ) )
2022-11-11 14:08:29 +01:00
}
continue
}
2020-12-31 09:33:24 +01:00
2022-11-11 14:08:29 +01:00
rollback := p . extend ( newWidth , newHeight )
if p . canAlloc ( p . root , width , height ) {
return true
}
rollback ( )
2020-12-31 09:33:24 +01:00
2022-11-11 14:08:29 +01:00
// If the allocation failed even with a maximized page, give up the allocation.
if newWidth >= p . maxSize && newHeight >= p . maxSize {
return false
}
}
2020-12-31 09:33:24 +01:00
}
2022-11-11 14:08:29 +01:00
}
2020-12-31 09:33:24 +01:00
2022-11-11 14:08:29 +01:00
func ( p * Page ) extend ( newWidth int , newHeight int ) func ( ) {
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 {
2022-11-11 14:08:29 +01:00
if n . x + n . width < p . width && n . y + n . height < p . height {
2020-12-31 09:33:24 +01:00
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
} )
}
2022-11-11 14:08:29 +01:00
var rollback func ( )
2018-03-10 17:13:53 +01:00
if aborted {
2019-11-17 16:23:10 +01:00
origRoot := * p . root
2022-11-12 09:21:38 +01:00
// Extend the page in the vertical direction.
if newHeight - p . height > 0 {
upper := p . root
lower := & Node {
x : 0 ,
y : p . height ,
width : p . width ,
height : newHeight - p . height ,
}
p . root = & Node {
x : 0 ,
y : 0 ,
width : p . width ,
height : newHeight ,
child0 : upper ,
child1 : lower ,
}
upper . parent = p . root
lower . parent = p . root
2018-03-10 17:13:53 +01:00
}
2022-11-12 09:21:38 +01:00
// Extend the page in the horizontal direction.
if newWidth - p . width > 0 {
left := p . root
right := & Node {
x : p . width ,
y : 0 ,
width : newWidth - p . width ,
height : newHeight ,
}
p . root = & Node {
x : 0 ,
y : 0 ,
width : newWidth ,
height : newHeight ,
child0 : left ,
child1 : right ,
}
left . parent = p . root
right . parent = p . root
2018-03-10 17:13:53 +01:00
}
2019-11-17 16:23:10 +01:00
2022-11-11 14:08:29 +01:00
origWidth , origHeight := p . width , p . height
rollback = func ( ) {
p . width = origWidth
p . height = origHeight
2019-11-17 16:23:10 +01:00
p . root = & origRoot
}
2018-03-10 17:13:53 +01:00
} else {
2022-11-11 14:08:29 +01:00
origWidth , origHeight := p . width , p . height
2019-11-17 16:23:10 +01:00
origWidths := map [ * Node ] int { }
origHeights := map [ * Node ] int { }
2018-03-10 17:13:53 +01:00
for _ , n := range edgeNodes {
2022-11-11 14:08:29 +01:00
if n . x + n . width == p . width {
2019-11-17 16:23:10 +01:00
origWidths [ n ] = n . width
2022-11-11 14:08:29 +01:00
n . width += newWidth - p . width
2018-03-10 17:13:53 +01:00
}
2022-11-11 14:08:29 +01:00
if n . y + n . height == p . height {
2019-11-17 16:23:10 +01:00
origHeights [ n ] = n . height
2022-11-11 14:08:29 +01:00
n . height += newHeight - p . height
2018-03-10 17:13:53 +01:00
}
}
2019-11-17 16:23:10 +01:00
2022-11-11 14:08:29 +01:00
rollback = func ( ) {
p . width = origWidth
p . height = origHeight
2019-11-17 16:23:10 +01:00
for n , w := range origWidths {
n . width = w
}
for n , h := range origHeights {
n . height = h
}
}
2018-03-10 17:13:53 +01:00
}
2022-11-11 14:08:29 +01:00
p . width = newWidth
p . height = newHeight
2020-12-31 09:33:24 +01:00
2022-11-11 14:08:29 +01:00
return rollback
2018-03-10 17:13:53 +01:00
}