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"
2023-04-27 15:34:42 +02:00
"image"
2018-03-10 17:13:53 +01:00
)
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 {
2023-04-27 15:34:42 +02:00
region image . Rectangle
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 ( )
}
2023-04-27 15:34:42 +02:00
func ( n * Node ) Region ( ) image . Rectangle {
return n . region
2018-02-25 12:16:27 +01:00
}
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 alloc ( n * Node , width , height int ) * Node {
2023-04-27 15:34:42 +02:00
if n . region . Dx ( ) < width || n . region . Dy ( ) < height {
2018-02-25 12:16:27 +01:00
return nil
}
if n . used {
return nil
}
if n . child0 == nil && n . child1 == nil {
2023-04-27 15:34:42 +02:00
if n . region . Dx ( ) == width && n . region . Dy ( ) == height {
2018-02-25 12:16:27 +01:00
n . used = true
return n
}
2023-04-27 15:34:42 +02:00
if square ( n . region . Dx ( ) - width , n . region . Dy ( ) ) >= square ( n . region . Dx ( ) , n . region . Dy ( ) - height ) {
2018-02-25 12:16:27 +01:00
// Split vertically
n . child0 = & Node {
2023-04-27 15:34:42 +02:00
r egion : image . Rect ( n . region . Min . X , n . region . Min . Y , n . region . Min . X + width , n . region . Max . Y ) ,
2018-02-25 12:16:27 +01:00
parent : n ,
}
n . child1 = & Node {
2023-04-27 15:34:42 +02:00
region : image . Rect ( n . region . Min . X + width , n . region . Min . Y , n . region . Max . X , n . region . Max . Y ) ,
2018-02-25 12:16:27 +01:00
parent : n ,
}
} else {
2023-01-28 11:06:38 +01:00
// Split horizontally
2018-02-25 12:16:27 +01:00
n . child0 = & Node {
2023-04-27 15:34:42 +02:00
region : image . Rect ( n . region . Min . X , n . region . Min . Y , n . region . Max . X , n . region . Min . Y + height ) ,
2018-02-25 12:16:27 +01:00
parent : n ,
}
n . child1 = & Node {
2023-04-27 15:34:42 +02:00
region : image . Rect ( n . region . Min . X , n . region . Min . Y + height , n . region . Max . X , n . region . Max . Y ) ,
2018-02-25 12:16:27 +01:00
parent : n ,
}
}
2023-04-08 15:03:16 +02:00
// Note: it now MUST fit, due to above preconditions (repeated here).
2023-04-27 15:34:42 +02:00
if n . child0 . region . Dx ( ) < width || n . child0 . region . Dy ( ) < height {
panic ( fmt . Sprintf ( "packing: the newly created child node (%d, %d) unexpectedly does not contain the requested size (%d, %d)" , n . child0 . region . Dx ( ) , n . child0 . region . Dy ( ) , width , height ) )
2023-04-08 15:03:16 +02:00
}
// Thus, alloc can't return nil, but it may do another split along the other dimension
// to get a node with the exact size (width, height).
node := alloc ( n . child0 , width , height )
if node == nil {
2023-04-27 15:34:42 +02:00
panic ( fmt . Sprintf ( "packing: could not allocate the requested size (%d, %d) in the newly created child node (%d, %d)" , width , height , n . child0 . region . Dx ( ) , n . child0 . region . Dy ( ) ) )
2023-04-08 15:03:16 +02:00
}
return node
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
2018-02-25 12:16:27 +01:00
if p . root == nil {
p . root = & Node {
2023-04-27 15:34:42 +02:00
region : image . Rect ( 0 , 0 , p . width , p . height ) ,
2018-02-25 12:16:27 +01:00
}
}
2024-01-13 09:42:51 +01:00
return p . extendAndAlloc ( 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
}
2024-01-13 09:42:51 +01:00
func ( p * Page ) extendAndAlloc ( width , height int ) * Node {
2023-04-08 15:03:16 +02:00
if n := alloc ( p . root , width , height ) ; n != nil {
return n
2022-11-11 13:15:28 +01:00
}
2022-11-11 14:08:29 +01:00
if p . width >= p . maxSize && p . height >= p . maxSize {
2023-04-08 15:03:16 +02:00
return nil
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 )
2023-04-08 15:03:16 +02:00
if n := alloc ( p . root , width , height ) ; n != nil {
return n
2022-11-11 14:08:29 +01:00
}
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 {
2023-04-08 15:03:16 +02:00
return nil
2022-11-11 14:08:29 +01:00
}
}
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 {
2023-04-27 15:34:42 +02:00
if n . region . Max . X < p . width && n . region . Max . Y < 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 {
2023-02-28 09:06:51 +01:00
origRoot := p . root
origRootCloned := * p . root
2019-11-17 16:23:10 +01:00
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 {
2023-04-27 15:34:42 +02:00
region : image . Rect ( 0 , p . height , p . width , newHeight ) ,
2022-11-12 09:21:38 +01:00
}
p . root = & Node {
2023-04-27 15:34:42 +02:00
region : image . Rect ( 0 , 0 , p . width , newHeight ) ,
2022-11-12 09:21:38 +01:00
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 {
2023-04-27 15:34:42 +02:00
region : image . Rect ( p . width , 0 , newWidth , newHeight ) ,
2022-11-12 09:21:38 +01:00
}
p . root = & Node {
2023-04-27 15:34:42 +02:00
region : image . Rect ( 0 , 0 , newWidth , newHeight ) ,
2022-11-12 09:21:38 +01:00
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
2023-02-28 09:06:51 +01:00
// The node address must not be changed, so use the original root node's pointer (#2584).
2023-02-28 13:45:58 +01:00
// As the root node might be modified, restore the content by the cloned content.
2023-02-28 09:06:51 +01:00
p . root = origRoot
* p . root = origRootCloned
2019-11-17 16:23:10 +01:00
}
2018-03-10 17:13:53 +01:00
} else {
2022-11-11 14:08:29 +01:00
origWidth , origHeight := p . width , p . height
2023-04-27 15:34:42 +02:00
origMaxXs := map [ * Node ] int { }
origMaxYs := map [ * Node ] int { }
2019-11-17 16:23:10 +01:00
2018-03-10 17:13:53 +01:00
for _ , n := range edgeNodes {
2023-04-27 15:34:42 +02:00
if n . region . Max . X == p . width {
origMaxXs [ n ] = n . region . Max . X
n . region . Max . X = newWidth
2018-03-10 17:13:53 +01:00
}
2023-04-27 15:34:42 +02:00
if n . region . Max . Y == p . height {
origMaxYs [ n ] = n . region . Max . Y
n . region . Max . Y = newHeight
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
2023-04-27 15:34:42 +02:00
for n , x := range origMaxXs {
n . region . Max . X = x
2019-11-17 16:23:10 +01:00
}
2023-04-27 15:34:42 +02:00
for n , y := range origMaxYs {
n . region . Max . Y = y
2019-11-17 16:23:10 +01:00
}
}
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
}