Files
aoi/quadtree.go
2024-03-09 10:29:06 +08:00

209 lines
5.1 KiB
Go

package aoi
import "sync"
const (
leftUp int = iota
rightUp
leftDown
rightDown
maxCap = 500 // Maximum capacity of a node
maxDeep = 4 // Maximum depth of the quadtree
radius = 16 // Field of view radius
)
// Node represents a node in the quadtree.
type Node struct {
Leaf bool // Indicates whether the node is a leaf node
Deep int // Depth of the node in the quadtree
AreaWidth float64 // Width of the grid (assuming square grids)
XStart float64 // Starting X-coordinate of the node's area
YStart float64 // Starting Y-coordinate of the node's area
Tree *QuadTree // Pointer to the quadtree
Child [4]*Node // Child nodes (quadrants)
Entities *sync.Map // Entities within the node
}
// QuadTree represents a quadtree data structure for spatial partitioning.
type QuadTree struct {
maxCap, maxDeep int
radius float64
mPool sync.Pool
*Node
}
// NewSonNode creates a new child node with the specified parameters.
func NewSonNode(xStart, yStart float64, parent *Node) *Node {
son := &Node{
Leaf: true,
Deep: parent.Deep + 1,
AreaWidth: parent.AreaWidth / 2,
XStart: xStart,
YStart: yStart,
Tree: parent.Tree,
Entities: parent.Tree.mPool.Get().(*sync.Map),
}
return son
}
// canCut checks whether the node can be split.
func (n *Node) canCut() bool {
if n.XStart+n.AreaWidth/2 > 0 && n.YStart+n.AreaWidth/2 > 0 {
return true
}
return false
}
// needCut checks whether the node needs to be split.
func (n *Node) needCut() bool {
lens := 0
n.Entities.Range(func(key, value interface{}) bool {
lens++
return true
})
return lens+1 > n.Tree.maxCap && n.Deep+1 <= n.Tree.maxDeep && n.canCut()
}
// intersects checks if the coordinates are within the node's range.
func (n *Node) intersects(x, y float64) bool {
if n.XStart <= x && x < n.XStart+n.AreaWidth && n.YStart <= y && y < n.YStart+n.AreaWidth {
return true
}
return false
}
// findSonQuadrant finds the quadrant of a child node based on coordinates.
func (n *Node) findSonQuadrant(x, y float64) int {
if x < n.Child[rightDown].XStart {
if y < n.Child[rightDown].YStart {
return leftUp
}
return leftDown
}
if y < n.Child[rightDown].YStart {
return rightUp
}
return rightDown
}
// cutNode splits the node into four child nodes.
func (n *Node) cutNode() {
n.Leaf = false
half := n.AreaWidth / 2
n.Child[leftUp] = NewSonNode(n.XStart, n.YStart, n)
n.Child[rightUp] = NewSonNode(n.XStart+half, n.YStart, n)
n.Child[leftDown] = NewSonNode(n.XStart, n.YStart+half, n)
n.Child[rightDown] = NewSonNode(n.XStart+half, n.YStart+half, n)
// Move entities to the corresponding child nodes
n.Entities.Range(func(k, v interface{}) bool {
entity := v.(*Entity)
for _, node := range n.Child {
if node.intersects(entity.X, entity.Y) {
node.Entities.Store(entity.Key, entity)
}
}
n.Entities.Delete(k)
return true
})
n.Tree.mPool.Put(n.Entities)
n.Entities = nil
}
// NewQuadTree initializes a new QuadTree with the specified parameters.
func NewQuadTree(xStart, yStart, width float64) AOI {
basicNode := &Node{
Leaf: true,
Deep: 1,
AreaWidth: width,
XStart: xStart,
YStart: yStart,
Child: [4]*Node{},
}
tree := &QuadTree{
maxDeep: maxDeep,
maxCap: maxCap,
radius: radius,
Node: basicNode,
}
tree.mPool.New = func() interface{} {
return &sync.Map{}
}
basicNode.Tree = tree
basicNode.Entities = tree.mPool.Get().(*sync.Map)
return tree
}
// Add adds an entity to the quadtree based on its coordinates.
func (n *Node) Add(x, y float64, name string) {
// Check if splitting is required
if n.Leaf && n.needCut() {
n.cutNode()
}
// Recursively add to non-leaf nodes
if !n.Leaf {
n.Child[n.findSonQuadrant(x, y)].Add(x, y, name)
return
}
entity := entityPool.Get().(*Entity)
entity.X = x
entity.Y = y
entity.Key = name
// Store in leaf node
n.Entities.Store(entity.Key, entity)
}
// Delete removes an entity from the quadtree based on its coordinates.
func (n *Node) Delete(x, y float64, name string) {
if !n.Leaf {
n.Child[n.findSonQuadrant(x, y)].Delete(x, y, name)
return
}
if entity, ok := n.Entities.Load(name); ok {
n.Entities.Delete(name)
entityPool.Put(entity)
}
}
// Search retrieves a list of entity keys within the specified coordinates' range.
func (n *Node) Search(x, y float64) []string {
result := resultPool.Get().([]string)
defer func() {
result = result[:0]
resultPool.Put(result)
}()
n.search(x, y, &result)
return result
}
// search recursively searches for entities within the specified coordinates' range.
func (n *Node) search(x, y float64, result *[]string) {
if !n.Leaf {
minX, maxX := x-n.Tree.radius, x+n.Tree.radius
minY, maxY := y-n.Tree.radius, y+n.Tree.radius
for _, son := range n.Child {
if son.intersects(minX, minY) || son.intersects(maxX, minY) ||
son.intersects(minX, maxY) || son.intersects(maxX, maxY) {
son.search(x, y, result)
}
}
return
}
// Collect entity keys within the leaf node
n.Entities.Range(func(key, value interface{}) bool {
*result = append(*result, value.(*Entity).Key)
return true
})
return
}