mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2025-01-26 02:42:02 +01:00
Add internal/bsp (#514)
This commit is contained in:
parent
c7a733fb16
commit
b88b86e0e7
148
internal/bsp/bsp.go
Normal file
148
internal/bsp/bsp.go
Normal file
@ -0,0 +1,148 @@
|
||||
// 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.
|
||||
|
||||
// Package bsp offers binary space partitioning algorithm.
|
||||
package bsp
|
||||
|
||||
const (
|
||||
MaxSize = 1024
|
||||
minSize = 4
|
||||
)
|
||||
|
||||
type Page struct {
|
||||
root *Node
|
||||
}
|
||||
|
||||
type Node struct {
|
||||
x int
|
||||
y int
|
||||
width int
|
||||
height int
|
||||
|
||||
used bool
|
||||
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
|
||||
}
|
||||
|
||||
func (n *Node) alloc(width, height int) *Node {
|
||||
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
|
||||
}
|
||||
if n.height == height || (n.width != width && width <= height) {
|
||||
// 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,
|
||||
}
|
||||
return n.child0.alloc(width, height)
|
||||
} 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,
|
||||
}
|
||||
return n.child0.alloc(width, height)
|
||||
}
|
||||
}
|
||||
if n.child0 == nil || n.child1 == nil {
|
||||
panic("not reached")
|
||||
}
|
||||
if node := n.child0.alloc(width, height); node != nil {
|
||||
return node
|
||||
}
|
||||
if node := n.child1.alloc(width, height); node != nil {
|
||||
return node
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Page) Alloc(width, height int) *Node {
|
||||
if width <= 0 || height <= 0 {
|
||||
panic("bsp: width and height must > 0")
|
||||
}
|
||||
if p.root == nil {
|
||||
p.root = &Node{
|
||||
width: MaxSize,
|
||||
height: MaxSize,
|
||||
}
|
||||
}
|
||||
if width < minSize {
|
||||
width = minSize
|
||||
}
|
||||
if height < minSize {
|
||||
height = minSize
|
||||
}
|
||||
return p.root.alloc(width, height)
|
||||
}
|
||||
|
||||
func (p *Page) Free(node *Node) {
|
||||
if node.child0 != nil || node.child1 != nil {
|
||||
panic("bsp: can't free the node including children")
|
||||
}
|
||||
node.used = false
|
||||
if node.parent == nil {
|
||||
return
|
||||
}
|
||||
if node.parent.child0 == nil || node.parent.child1 == nil {
|
||||
panic("not reached")
|
||||
}
|
||||
if node.parent.child0.canFree() && node.parent.child1.canFree() {
|
||||
node.parent.child0 = nil
|
||||
node.parent.child1 = nil
|
||||
p.Free(node.parent)
|
||||
}
|
||||
}
|
208
internal/bsp/bsp_test.go
Normal file
208
internal/bsp/bsp_test.go
Normal file
@ -0,0 +1,208 @@
|
||||
// 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.
|
||||
|
||||
package bsp_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/hajimehoshi/ebiten/internal/bsp"
|
||||
)
|
||||
|
||||
func TestBSP(t *testing.T) {
|
||||
type Rect struct {
|
||||
X int
|
||||
Y int
|
||||
Width int
|
||||
Height int
|
||||
}
|
||||
|
||||
type Op struct {
|
||||
Width int
|
||||
Height int
|
||||
FreeNodeID int
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
In []Op
|
||||
Out []*Rect
|
||||
}{
|
||||
{
|
||||
In: []Op{
|
||||
{100, 100, -1},
|
||||
{100, 100, -1},
|
||||
{100, 100, -1},
|
||||
{100, 100, -1},
|
||||
{100, 100, -1},
|
||||
{100, 100, -1},
|
||||
{0, 0, 1},
|
||||
{0, 0, 3},
|
||||
{0, 0, 5},
|
||||
{0, 0, 0},
|
||||
{0, 0, 2},
|
||||
{0, 0, 4},
|
||||
{MaxSize, MaxSize, -1},
|
||||
},
|
||||
Out: []*Rect{
|
||||
{0, 0, 100, 100},
|
||||
{0, 100, 100, 100},
|
||||
{0, 200, 100, 100},
|
||||
{0, 300, 100, 100},
|
||||
{0, 400, 100, 100},
|
||||
{0, 500, 100, 100},
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
{0, 0, MaxSize, MaxSize},
|
||||
},
|
||||
},
|
||||
{
|
||||
In: []Op{
|
||||
{100, 200, -1},
|
||||
{MaxSize, MaxSize, -1},
|
||||
{100, 200, -1},
|
||||
{50, 50, -1},
|
||||
{200, 200, -1},
|
||||
{MaxSize, MaxSize, -1},
|
||||
{500, 500, -1},
|
||||
{600, 600, -1},
|
||||
{100, 100, -1},
|
||||
{0, 0, 2},
|
||||
{100, 200, -1},
|
||||
},
|
||||
Out: []*Rect{
|
||||
{0, 0, 100, 200},
|
||||
nil,
|
||||
{0, 200, 100, 200},
|
||||
{0, 400, 50, 50},
|
||||
{100, 0, 200, 200},
|
||||
nil,
|
||||
{300, 0, 500, 500},
|
||||
nil,
|
||||
{100, 200, 100, 100},
|
||||
nil,
|
||||
{0, 200, 100, 200},
|
||||
},
|
||||
},
|
||||
{
|
||||
In: []Op{
|
||||
{256, 256, -1},
|
||||
{256, 256, -1},
|
||||
{256, 256, -1},
|
||||
{256, 256, -1},
|
||||
|
||||
{256, 256, -1},
|
||||
{256, 256, -1},
|
||||
{256, 256, -1},
|
||||
{256, 256, -1},
|
||||
|
||||
{256, 256, -1},
|
||||
{256, 256, -1},
|
||||
{256, 256, -1},
|
||||
{256, 256, -1},
|
||||
|
||||
{256, 256, -1},
|
||||
{256, 256, -1},
|
||||
{256, 256, -1},
|
||||
{256, 256, -1},
|
||||
|
||||
{256, 256, -1},
|
||||
},
|
||||
Out: []*Rect{
|
||||
{0, 0, 256, 256},
|
||||
{0, 256, 256, 256},
|
||||
{0, 512, 256, 256},
|
||||
{0, 768, 256, 256},
|
||||
|
||||
{256, 0, 256, 256},
|
||||
{256, 256, 256, 256},
|
||||
{256, 512, 256, 256},
|
||||
{256, 768, 256, 256},
|
||||
|
||||
{512, 0, 256, 256},
|
||||
{512, 256, 256, 256},
|
||||
{512, 512, 256, 256},
|
||||
{512, 768, 256, 256},
|
||||
|
||||
{768, 0, 256, 256},
|
||||
{768, 256, 256, 256},
|
||||
{768, 512, 256, 256},
|
||||
{768, 768, 256, 256},
|
||||
|
||||
nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
In: []Op{
|
||||
{300, 300, -1},
|
||||
{300, 300, -1},
|
||||
{300, 300, -1},
|
||||
{300, 300, -1},
|
||||
{300, 300, -1},
|
||||
{300, 300, -1},
|
||||
{300, 300, -1},
|
||||
{300, 300, -1},
|
||||
{300, 300, -1},
|
||||
{300, 300, -1},
|
||||
},
|
||||
Out: []*Rect{
|
||||
{0, 0, 300, 300},
|
||||
{0, 300, 300, 300},
|
||||
{0, 600, 300, 300},
|
||||
{300, 0, 300, 300},
|
||||
{300, 300, 300, 300},
|
||||
{300, 600, 300, 300},
|
||||
{600, 0, 300, 300},
|
||||
{600, 300, 300, 300},
|
||||
{600, 600, 300, 300},
|
||||
nil,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for caseIndex, c := range cases {
|
||||
p := &Page{}
|
||||
nodes := []*Node{}
|
||||
for _, in := range c.In {
|
||||
if in.FreeNodeID == -1 {
|
||||
n := p.Alloc(in.Width, in.Height)
|
||||
nodes = append(nodes, n)
|
||||
} else {
|
||||
p.Free(nodes[in.FreeNodeID])
|
||||
nodes = append(nodes, nil)
|
||||
}
|
||||
}
|
||||
for i, out := range c.Out {
|
||||
if nodes[i] == nil {
|
||||
if out != nil {
|
||||
t.Errorf("(%d) nodes[%d]: should be nil but %v", caseIndex, i, out)
|
||||
}
|
||||
continue
|
||||
}
|
||||
x, y, width, height := nodes[i].Region()
|
||||
got := Rect{x, y, width, height}
|
||||
if out == nil {
|
||||
t.Errorf("(%d) nodes[%d]: got: %v, want: %v", caseIndex, i, got, nil)
|
||||
continue
|
||||
}
|
||||
want := *out
|
||||
if got != want {
|
||||
t.Errorf("(%d) nodes[%d]: got: %v, want: %v", caseIndex, i, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user