diff --git a/README.md b/README.md index 6834c99..7307c9c 100644 --- a/README.md +++ b/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) \ No newline at end of file diff --git a/aoi.go b/aoi.go index e7d059f..904ea45 100644 --- a/aoi.go +++ b/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{} } diff --git a/grid.go b/grid.go index aec2d2e..304eb89 100644 --- a/grid.go +++ b/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) diff --git a/grid_test.go b/grid_test.go index a2b7a08..84d6613 100644 --- a/grid_test.go +++ b/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++ { diff --git a/quadtree.go b/quadtree.go index 8a138a6..9c6cd68 100644 --- a/quadtree.go +++ b/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