quadtree to fast
zswDev opened this issue · 0 comments
zswDev commented
Reduce memory allocation
package fast
import (
"room/business/tool"
"sync"
)
//https://github.com/EngoEngine/engo
var (
quadtreeNodePool *sync.Pool
)
const minQuadtreeCellSize = 0.01
func init() {
quadtreeNodePool = &sync.Pool{
New: func() interface{} {
return &quadtreeNode{
Objects: []quadtreeNodeData{},
}
},
}
}
type AABB = tool.AABB
type Point = tool.Point
type AABBer = tool.AABBer
// type AABB struct {
// Min, Max Point
// }
// // AABBer is an interface for everything that provides information about its axis aligned bounding box.
// type AABBer interface {
// // AABB returns the axis aligned bounding box.
// AABB() AABB
// }
func aabbOverlaps(a, b AABB) bool {
// a is left of b
if a.Max.X < b.Min.X {
return false
}
// a is right of b
if a.Min.X > b.Max.X {
return false
}
// a is above b
if a.Max.Y < b.Min.Y {
return false
}
// a is below b
if a.Min.Y > b.Max.Y {
return false
}
// The two overlap
return true
}
func aabbWidth(x AABB) float64 {
return x.Max.X - x.Min.X
}
func aabbHeight(x AABB) float64 {
return x.Max.Y - x.Min.Y
}
func aabbRect(x, y, width, height float64) AABB {
return AABB{
Min: Point{
X: x,
Y: y,
},
Max: Point{
X: x + width,
Y: y + height,
},
}
}
type quadtreeNodeData struct {
Value AABBer
AABB AABB
}
type quadtreeNode struct {
hasNodes bool
Level int
Bounds AABB
Tree *Quadtree
Nodes [4]*quadtreeNode
Objects []quadtreeNodeData
}
// Quadtree implementation which can store AABBer values
type Quadtree struct {
MaxObjects int // Maximum objects a node can hold before splitting into 4 subnodes
MaxLevels int // Total max levels inside root Quadtree
Total int
root *quadtreeNode
result []AABBer
}
func calcMaxLevel(width, height float64) int {
res := 0
for width > minQuadtreeCellSize && height > minQuadtreeCellSize {
res++
width, height = width/2, height/2
}
return res
}
// NewQuadtree creates a new quadtree for the given bounds.
// When setting usePool to true, the internal values will be taken from a sync.Pool which reduces the allocation overhead.
// maxObjects tells the tree how many objects should be stored within a level before the quadtree cell is split.
func NewQuadtree(bounds AABB, maxObjects int) *Quadtree {
qt := &Quadtree{
MaxObjects: maxObjects,
result: []tool.AABBer{},
}
qt.root = qt.newNode(bounds, 0)
qt.MaxLevels = calcMaxLevel(aabbWidth(bounds), aabbHeight(bounds))
return qt
}
// Destroy frees the nodes if the Quadtree uses the node pool
func (qt *Quadtree) Destroy() {
qt.freeQuadtreeNode(qt.root)
qt.root = nil
}
func (qt *Quadtree) newNode(bounds AABB, level int) (node *quadtreeNode) {
node = quadtreeNodePool.Get().(*quadtreeNode)
if node == nil {
node = &quadtreeNode{
Objects: []quadtreeNodeData{},
}
}
node.Tree = qt
node.Bounds = bounds
node.Level = level
return node
}
func (qt *Quadtree) freeQuadtreeNode(n *quadtreeNode) {
if n.hasNodes {
for i := range n.Nodes {
qt.freeQuadtreeNode(n.Nodes[i])
n.Nodes[i] = nil
}
}
n.Objects = n.Objects[:0]
n.Tree = nil
n.hasNodes = false
quadtreeNodePool.Put(n)
}
// split - split the node into 4 subnodes
func (qt *quadtreeNode) split() {
if qt.hasNodes {
return
}
qt.hasNodes = true
nextLevel := qt.Level + 1
subWidth := aabbWidth(qt.Bounds) / 2
subHeight := aabbHeight(qt.Bounds) / 2
x := qt.Bounds.Min.X
y := qt.Bounds.Min.Y
//top right node (0)
qt.Nodes[0] = qt.Tree.newNode(aabbRect(x+subWidth, y, subWidth, subHeight), nextLevel)
//top left node (1)
qt.Nodes[1] = qt.Tree.newNode(aabbRect(x, y, subWidth, subHeight), nextLevel)
//bottom left node (2)
qt.Nodes[2] = qt.Tree.newNode(aabbRect(x, y+subHeight, subWidth, subHeight), nextLevel)
//bottom right node (3)
qt.Nodes[3] = qt.Tree.newNode(aabbRect(x+subWidth, y+subHeight, subWidth, subHeight), nextLevel)
}
func (qt *quadtreeNode) isEmpty() bool {
return len(qt.Objects) == 0 && !qt.hasNodes
}
func (qt *quadtreeNode) unsplit() {
for i := 0; i < 4; i++ {
if !qt.Nodes[i].isEmpty() {
return
}
}
for i := 0; i < 4; i++ {
qt.Tree.freeQuadtreeNode(qt.Nodes[i])
qt.Nodes[i] = nil
}
qt.hasNodes = false
}
// getIndex - Determine which quadrant the object belongs to (0-3)
func (qt *quadtreeNode) getIndex(pRect AABB) int {
horzMidpoint := qt.Bounds.Min.X + (aabbWidth(qt.Bounds) / 2)
vertMidpoint := qt.Bounds.Min.Y + (aabbHeight(qt.Bounds) / 2)
//pRect can completely fit within the top quadrants
topQuadrant := (pRect.Min.Y < vertMidpoint) && (pRect.Max.Y < vertMidpoint)
//pRect can completely fit within the bottom quadrants
bottomQuadrant := (pRect.Min.Y > vertMidpoint)
//pRect can completely fit within the left quadrants
if (pRect.Min.X < horzMidpoint) && (pRect.Max.X < horzMidpoint) {
if topQuadrant {
return 1
} else if bottomQuadrant {
return 2
}
} else if pRect.Min.X > horzMidpoint {
//pRect can completely fit within the right quadrants
if topQuadrant {
return 0
} else if bottomQuadrant {
return 3
}
}
return -1 // index of the subnode (0-3), or -1 if pRect cannot completely fit within a subnode and is part of the parent node
}
// Insert inserts the given item to the quadtree
func (qt *Quadtree) Insert(item AABBer) {
qt.Total++
qt.root.Insert(quadtreeNodeData{AABB: item.AABB(), Value: item})
}
func (qt *quadtreeNode) Insert(item quadtreeNodeData) {
if qt.hasNodes {
index := qt.getIndex(item.AABB)
if index != -1 {
qt.Nodes[index].Insert(item)
return
}
}
// If we don't subnodes within the Quadtree
qt.Objects = append(qt.Objects, item)
// If total objects is greater than max objects and level is less than max levels
if (len(qt.Objects) > qt.Tree.MaxObjects) && (qt.Tree.MaxLevels <= 0 || qt.Level < qt.Tree.MaxLevels) {
// split if we don't already have subnodes
if !qt.hasNodes {
qt.split()
}
// Add all objects to there corresponding subNodes
olen := len(qt.Objects)
for i := 0; i < len(qt.Objects); {
object := qt.Objects[i] // Get the object out of the slice
bounds := object.AABB
index := qt.getIndex(bounds)
if index != -1 {
qt.Objects = removeObjects(qt.Objects, i, olen)
olen--
qt.Nodes[index].Insert(object)
} else {
i++
}
}
}
}
func removeObjects(objects []quadtreeNodeData, delIdx, olen int) []quadtreeNodeData {
endIdx := olen - 1
if endIdx != delIdx {
objects[delIdx] = objects[endIdx]
}
return objects[:endIdx]
}
func (qt *quadtreeNode) Remove(item AABBer, pRect AABB) {
if qt.hasNodes {
index := qt.getIndex(pRect)
if index != -1 {
qt.Nodes[index].Remove(item, pRect)
qt.unsplit()
return
}
}
olen := len(qt.Objects)
for i := 0; i < len(qt.Objects); i++ {
if qt.Objects[i].Value == item {
removeObjects(qt.Objects, i, olen)
return
}
}
}
// Remove removes the given item from the quadtree
func (qt *Quadtree) Remove(item AABBer) {
bounds := item.AABB()
qt.root.Remove(item, bounds)
}
// Retrieve returns all objects that could collide with the given bounding box
func (qt *quadtreeNode) Retrieve(pRect AABB, result []tool.AABBer, filter func(aabb AABBer) bool) []AABBer {
index := qt.getIndex(pRect)
// Array with all detected objects
for i := range qt.Objects {
if aabbOverlaps(pRect, qt.Objects[i].Value.AABB()) &&
(filter == nil || filter(qt.Objects[i].Value)) {
result = append(result, qt.Objects[i].Value)
}
}
//if we have subnodes ...
if qt.hasNodes {
//if pRect fits into a subnode ..
if index != -1 {
result = qt.Nodes[index].Retrieve(pRect, result, filter)
} else {
//if pRect does not fit into a subnode, check it against all subnodes
for i := 0; i < 4; i++ {
result = qt.Nodes[i].Retrieve(pRect, result, filter)
}
}
}
return result
}
// Retrieve returns all objects that could collide with the given bounding box and passing the given filter function.
func (qt *Quadtree) Retrieve(find AABB, filter func(aabb AABBer) bool) []AABBer {
qt.result = qt.result[:0]
potentials := qt.root.Retrieve(find, qt.result, filter)
return potentials
}
// Clear removes all items from the quadtree
func (qt *Quadtree) Clear() {
bounds := qt.root.Bounds
qt.freeQuadtreeNode(qt.root)
qt.root = qt.newNode(bounds, 0)
qt.Total = 0
qt.result = qt.result[:0]
}