mirror of
https://github.com/knight0zh/aoi.git
synced 2026-02-03 15:44:46 +08:00
209 lines
5.1 KiB
Go
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
|
|
}
|