mirror of
https://github.com/knight0zh/aoi.git
synced 2026-02-15 02:14:41 +08:00
更新readme和注解
This commit is contained in:
42
README.md
42
README.md
@@ -1,7 +1,37 @@
|
||||
# AOI
|
||||
### Area of Interest Library
|
||||
### 调研学习实现一些AOI兴趣区算法
|
||||
# AOI (Area of Interest) Library
|
||||
|
||||
#### 目前实现:
|
||||
- 九宫格
|
||||
- 四叉树
|
||||
This library provides implementations of Area of Interest algorithms for spatial partitioning. Currently, it includes implementations for the following algorithms:
|
||||
|
||||
1. **九宫格 (Grid Manager)**
|
||||
- A simple grid-based AOI algorithm dividing the area into a grid of cells and associating entities with the corresponding grid cells.
|
||||
|
||||
2. **四叉树 (Quadtree)**
|
||||
- A hierarchical spatial partitioning algorithm dividing the area into four quadrants recursively, optimizing the search for entities within a specified range.
|
||||
|
||||
## Usage:
|
||||
|
||||
### 九宫格 (Grid Manager)
|
||||
|
||||
```go
|
||||
// Example Usage:
|
||||
aoiManager := NewGridManager(startX, startY, areaWidth, gridCount)
|
||||
aoiManager.Add(x, y, "Entity1")
|
||||
aoiManager.Delete(x, y, "Entity1")
|
||||
result := aoiManager.Search(x, y)
|
||||
|
||||
// Example Usage:
|
||||
quadTree := NewQuadTree(startX, startY, areaWidth)
|
||||
quadTree.Add(x, y, "Entity1")
|
||||
quadTree.Delete(x, y, "Entity1")
|
||||
result := quadTree.Search(x, y)
|
||||
```
|
||||
|
||||
## Features:
|
||||
- Both implementations support adding, deleting, and searching for entities within a specified area of interest.
|
||||
- The Grid Manager uses a simple grid-based approach, while the Quadtree provides a hierarchical and optimized solution for larger and dynamic environments.
|
||||
|
||||
## TODO:
|
||||
Implement additional commonly used AOI algorithms:
|
||||
- R-树 (R-tree)
|
||||
- 六边形网格 (Hexagonal Grid)
|
||||
- 基于事件的算法 (Event-driven Approaches)
|
||||
15
aoi.go
15
aoi.go
@@ -2,26 +2,31 @@ package aoi
|
||||
|
||||
import "sync"
|
||||
|
||||
// AOI (Area of Interest) represents an interface for managing entities within a specific area.
|
||||
type AOI interface {
|
||||
Add(x, y float64, name string) // 添加实体
|
||||
Delete(x, y float64, name string) // 移除实体
|
||||
Search(x, y float64) (result []string) // 范围查询
|
||||
Add(x, y float64, name string) // Add an entity to the AOI
|
||||
Delete(x, y float64, name string) // Delete an entity from the AOI
|
||||
Search(x, y float64) (result []string) // Search for entities within a specified range
|
||||
}
|
||||
|
||||
// Entity represents an object with coordinates and a key.
|
||||
type Entity struct {
|
||||
X, Y float64
|
||||
Key string
|
||||
}
|
||||
|
||||
var (
|
||||
resultPool sync.Pool
|
||||
entityPool sync.Pool
|
||||
resultPool sync.Pool // Pool for recycling result slices
|
||||
entityPool sync.Pool // Pool for recycling Entity objects
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Initialize the resultPool to recycle result slices
|
||||
resultPool.New = func() interface{} {
|
||||
return make([]string, 0, 500)
|
||||
}
|
||||
|
||||
// Initialize the entityPool to recycle Entity objects
|
||||
entityPool.New = func() interface{} {
|
||||
return &Entity{}
|
||||
}
|
||||
|
||||
56
grid.go
56
grid.go
@@ -3,35 +3,37 @@ package aoi
|
||||
import "sync"
|
||||
|
||||
var (
|
||||
// 分别将这8个方向的方向向量按顺序写入x, y的分量数组
|
||||
// Define direction vectors for the eight directions by populating dx and dy arrays.
|
||||
dx = []int{-1, -1, -1, 0, 0, 1, 1, 1}
|
||||
dy = []int{-1, 0, 1, -1, 1, -1, 0, 1}
|
||||
)
|
||||
|
||||
// Grid 格子
|
||||
// Grid represents a grid with a unique identifier and entities within it.
|
||||
type Grid struct {
|
||||
GID int //格子ID
|
||||
Entities sync.Map //当前格子内的实体
|
||||
GID int // Grid ID
|
||||
Entities sync.Map // Entities within the current grid
|
||||
}
|
||||
|
||||
// GridManger AOI九宫格实现矩形
|
||||
type GridManger struct {
|
||||
StartX int // X区域左边界坐标
|
||||
StartY int // Y区域上边界坐标
|
||||
AreaWidth int // 格子宽度(长=宽)
|
||||
GridCount int // 格子数量
|
||||
// GridManager implements AOI (Area of Interest) using a rectangular grid.
|
||||
type GridManager struct {
|
||||
StartX int // X-coordinate of the left boundary of the AOI
|
||||
StartY int // Y-coordinate of the upper boundary of the AOI
|
||||
AreaWidth int // Width of each grid (assuming square grids)
|
||||
GridCount int // Number of grids in each row/column
|
||||
grids map[int]*Grid
|
||||
pool sync.Pool
|
||||
}
|
||||
|
||||
// NewGrid creates a new grid with the specified ID.
|
||||
func NewGrid(gid int) *Grid {
|
||||
return &Grid{
|
||||
GID: gid,
|
||||
}
|
||||
}
|
||||
|
||||
func NewGridManger(startX, startY, areaWidth, gridCount int) AOI {
|
||||
manager := &GridManger{
|
||||
// NewGridManager initializes a new GridManager with the specified parameters.
|
||||
func NewGridManager(startX, startY, areaWidth, gridCount int) AOI {
|
||||
manager := &GridManager{
|
||||
StartX: startX,
|
||||
StartY: startY,
|
||||
AreaWidth: areaWidth,
|
||||
@@ -42,9 +44,10 @@ func NewGridManger(startX, startY, areaWidth, gridCount int) AOI {
|
||||
return make([]*Grid, 0, 9)
|
||||
}
|
||||
|
||||
// Initialize grids with unique IDs
|
||||
for y := 0; y < gridCount; y++ {
|
||||
for x := 0; x < gridCount; x++ {
|
||||
//格子编号:ID = IDy *nx + IDx (利用格子坐标得到格子编号)
|
||||
// Grid ID calculation: ID = IDy * nx + IDx (using grid coordinates to obtain grid ID)
|
||||
gID := y*gridCount + x
|
||||
manager.grids[gID] = NewGrid(gID)
|
||||
}
|
||||
@@ -53,20 +56,21 @@ func NewGridManger(startX, startY, areaWidth, gridCount int) AOI {
|
||||
return manager
|
||||
}
|
||||
|
||||
func (g *GridManger) gridWidth() int {
|
||||
// gridWidth calculates the width of each grid.
|
||||
func (g *GridManager) gridWidth() int {
|
||||
return g.AreaWidth / g.GridCount
|
||||
}
|
||||
|
||||
// getGIDByPos 通过横纵坐标获取对应的格子ID
|
||||
func (g *GridManger) getGIDByPos(x, y float64) int {
|
||||
// getGIDByPos calculates the grid ID based on the given coordinates.
|
||||
func (g *GridManager) getGIDByPos(x, y float64) int {
|
||||
gx := (int(x) - g.StartX) / g.gridWidth()
|
||||
gy := (int(y) - g.StartY) / g.gridWidth()
|
||||
|
||||
return gy*g.GridCount + gx
|
||||
}
|
||||
|
||||
// getSurroundGrids 根据格子的gID得到当前周边的九宫格信息
|
||||
func (g *GridManger) getSurroundGrids(gID int) []*Grid {
|
||||
// getSurroundGrids retrieves information about the surrounding nine grids based on the given grid ID.
|
||||
func (g *GridManager) getSurroundGrids(gID int) []*Grid {
|
||||
grids := g.pool.Get().([]*Grid)
|
||||
defer func() {
|
||||
grids = grids[:0]
|
||||
@@ -77,9 +81,11 @@ func (g *GridManger) getSurroundGrids(gID int) []*Grid {
|
||||
return grids
|
||||
}
|
||||
grids = append(grids, g.grids[gID])
|
||||
// 根据gID, 得到格子所在的坐标
|
||||
|
||||
// Calculate the coordinates of the grid based on the grid ID
|
||||
x, y := gID%g.GridCount, gID/g.GridCount
|
||||
|
||||
// Add information about the eight neighboring grids
|
||||
for i := 0; i < 8; i++ {
|
||||
newX := x + dx[i]
|
||||
newY := y + dy[i]
|
||||
@@ -92,7 +98,8 @@ func (g *GridManger) getSurroundGrids(gID int) []*Grid {
|
||||
return grids
|
||||
}
|
||||
|
||||
func (g *GridManger) Add(x, y float64, key string) {
|
||||
// Add adds an entity to the appropriate grid based on its coordinates.
|
||||
func (g *GridManager) Add(x, y float64, key string) {
|
||||
entity := entityPool.Get().(*Entity)
|
||||
entity.X = x
|
||||
entity.Y = y
|
||||
@@ -103,7 +110,8 @@ func (g *GridManger) Add(x, y float64, key string) {
|
||||
grid.Entities.Store(key, entity)
|
||||
}
|
||||
|
||||
func (g *GridManger) Delete(x, y float64, key string) {
|
||||
// Delete removes an entity from the grid based on its coordinates.
|
||||
func (g *GridManager) Delete(x, y float64, key string) {
|
||||
ID := g.getGIDByPos(x, y)
|
||||
grid := g.grids[ID]
|
||||
|
||||
@@ -113,14 +121,18 @@ func (g *GridManger) Delete(x, y float64, key string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GridManger) Search(x, y float64) []string {
|
||||
// Search retrieves a list of entity keys within the specified coordinates' range.
|
||||
func (g *GridManager) Search(x, y float64) []string {
|
||||
result := resultPool.Get().([]string)
|
||||
defer func() {
|
||||
result = result[:0]
|
||||
resultPool.Put(result)
|
||||
}()
|
||||
|
||||
ID := g.getGIDByPos(x, y)
|
||||
grids := g.getSurroundGrids(ID)
|
||||
|
||||
// Collect entity keys from the surrounding grids
|
||||
for _, grid := range grids {
|
||||
grid.Entities.Range(func(_, value interface{}) bool {
|
||||
result = append(result, value.(*Entity).Key)
|
||||
|
||||
25
grid_test.go
25
grid_test.go
@@ -2,27 +2,30 @@ package aoi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"math/rand"
|
||||
"sort"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
/**
|
||||
0 50 100 150 200
|
||||
-----------------------------
|
||||
/*
|
||||
*
|
||||
|
||||
0 50 100 150 200
|
||||
-----------------------------
|
||||
|
||||
0 | 0 1 2 3 4
|
||||
50 | 5 6 7 8 9
|
||||
100 | 10 11 12 13 14
|
||||
150 | 15 16 17 18 19
|
||||
200 | 20 21 22 23 24
|
||||
|
||||
*/
|
||||
func TestGridManger_GetSurroundGrids(t *testing.T) {
|
||||
aol := NewGridManger(0, 0, 250, 5)
|
||||
manger := aol.(*GridManger)
|
||||
aol := NewGridManager(0, 0, 250, 5)
|
||||
manger := aol.(*GridManager)
|
||||
tests := []struct {
|
||||
x, y float64
|
||||
want []int
|
||||
@@ -62,8 +65,8 @@ func TestGridManger_GetSurroundGrids(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNewGridManger(t *testing.T) {
|
||||
aol := NewGridManger(0, 0, 250, 5)
|
||||
manger := aol.(*GridManger)
|
||||
aol := NewGridManager(0, 0, 250, 5)
|
||||
manger := aol.(*GridManager)
|
||||
entities := []*Entity{
|
||||
{
|
||||
X: 0, Y: 0, Key: "a",
|
||||
@@ -118,8 +121,8 @@ func TestNewGridManger(t *testing.T) {
|
||||
|
||||
func BenchmarkGridManger(b *testing.B) {
|
||||
var wg sync.WaitGroup
|
||||
aol := NewGridManger(0, 0, 1024, 16)
|
||||
manger := aol.(*GridManger)
|
||||
aol := NewGridManager(0, 0, 1024, 16)
|
||||
manger := aol.(*GridManager)
|
||||
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
for i := 0; i < b.N; i++ {
|
||||
|
||||
49
quadtree.go
49
quadtree.go
@@ -8,22 +8,24 @@ const (
|
||||
leftDown
|
||||
rightDown
|
||||
|
||||
maxCap = 500 // 节点最大容量
|
||||
maxDeep = 4 // 节点最大深度
|
||||
radius = 16 // 视野半径
|
||||
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 // 是否为叶子节点
|
||||
Deep int // 深度
|
||||
AreaWidth float64 // 格子宽度(长=宽)
|
||||
XStart float64 // 起始范围
|
||||
YStart float64 // 起始范围
|
||||
Tree *QuadTree // 树指针
|
||||
Child [4]*Node // 子节点
|
||||
Entities *sync.Map // 实体
|
||||
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
|
||||
@@ -31,6 +33,7 @@ type QuadTree struct {
|
||||
*Node
|
||||
}
|
||||
|
||||
// NewSonNode creates a new child node with the specified parameters.
|
||||
func NewSonNode(xStart, yStart float64, parent *Node) *Node {
|
||||
son := &Node{
|
||||
Leaf: true,
|
||||
@@ -45,7 +48,7 @@ func NewSonNode(xStart, yStart float64, parent *Node) *Node {
|
||||
return son
|
||||
}
|
||||
|
||||
// canCut 检查节点是否可以分割
|
||||
// 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
|
||||
@@ -53,7 +56,7 @@ func (n *Node) canCut() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// needCut 检查节点是否需要分割
|
||||
// needCut checks whether the node needs to be split.
|
||||
func (n *Node) needCut() bool {
|
||||
lens := 0
|
||||
n.Entities.Range(func(key, value interface{}) bool {
|
||||
@@ -63,7 +66,7 @@ func (n *Node) needCut() bool {
|
||||
return lens+1 > n.Tree.maxCap && n.Deep+1 <= n.Tree.maxDeep && n.canCut()
|
||||
}
|
||||
|
||||
// intersects 检查坐标是否在节点范围内
|
||||
// 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
|
||||
@@ -71,7 +74,7 @@ func (n *Node) intersects(x, y float64) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// findSonQuadrant 根据坐标寻找子节点的方位
|
||||
// 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 {
|
||||
@@ -85,7 +88,7 @@ func (n *Node) findSonQuadrant(x, y float64) int {
|
||||
return rightDown
|
||||
}
|
||||
|
||||
// cutNode 分割节点
|
||||
// cutNode splits the node into four child nodes.
|
||||
func (n *Node) cutNode() {
|
||||
n.Leaf = false
|
||||
half := n.AreaWidth / 2
|
||||
@@ -95,7 +98,7 @@ func (n *Node) cutNode() {
|
||||
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 {
|
||||
@@ -111,6 +114,7 @@ func (n *Node) cutNode() {
|
||||
n.Entities = nil
|
||||
}
|
||||
|
||||
// NewQuadTree initializes a new QuadTree with the specified parameters.
|
||||
func NewQuadTree(xStart, yStart, width float64) AOI {
|
||||
basicNode := &Node{
|
||||
Leaf: true,
|
||||
@@ -134,13 +138,14 @@ func NewQuadTree(xStart, yStart, width float64) AOI {
|
||||
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
|
||||
@@ -151,10 +156,11 @@ func (n *Node) Add(x, y float64, name string) {
|
||||
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)
|
||||
@@ -167,6 +173,7 @@ func (n *Node) Delete(x, y float64, name string) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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() {
|
||||
@@ -177,6 +184,7 @@ func (n *Node) Search(x, y float64) []string {
|
||||
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
|
||||
@@ -191,6 +199,7 @@ func (n *Node) search(x, y float64, result *[]string) {
|
||||
return
|
||||
}
|
||||
|
||||
// Collect entity keys within the leaf node
|
||||
n.Entities.Range(func(key, value interface{}) bool {
|
||||
*result = append(*result, value.(*Entity).Key)
|
||||
return true
|
||||
|
||||
Reference in New Issue
Block a user