实现aoi九宫格算法

This commit is contained in:
knight0zh
2022-08-15 17:53:52 +08:00
parent 87a17cb7b9
commit 61954a83e5
11 changed files with 385 additions and 0 deletions

8
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

9
.idea/aoi.iml generated Normal file
View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/aoi.iml" filepath="$PROJECT_DIR$/.idea/aoi.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

12
aoi.go Normal file
View File

@@ -0,0 +1,12 @@
package aoi
type AOI interface {
Add(entity *Entity) // 添加实体
Delete(entity *Entity) // 移除实体
Search(entity *Entity) (result []*Entity) // 范围查询
}
type Entity struct {
X, Y float64
Name string
}

11
go.mod Normal file
View File

@@ -0,0 +1,11 @@
module github.com/knight0zh/aoi
go 1.17
require github.com/stretchr/testify v1.8.0
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

15
go.sum Normal file
View File

@@ -0,0 +1,15 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

112
grid.go Normal file
View File

@@ -0,0 +1,112 @@
package aoi
import "sync"
// Grid 格子
type Grid struct {
GID int //格子ID
Entities sync.Map //当前格子内的实体
}
// GridManger AOI九宫格实现矩形
type GridManger struct {
StartX int // X区域左边界坐标
StartY int // Y区域上边界坐标
AreaWidth int // 格子宽度(长=宽)
GridCount int // 格子数量
grids map[int]*Grid
}
func NewGrid(gid int) *Grid {
return &Grid{
GID: gid,
}
}
func NewGridManger(startX, startY, areaWidth, gridCount int) AOI {
manager := &GridManger{
StartX: startX,
StartY: startY,
AreaWidth: areaWidth,
GridCount: gridCount,
grids: make(map[int]*Grid),
}
for y := 0; y < gridCount; y++ {
for x := 0; x < gridCount; x++ {
//格子编号ID = IDy *nx + IDx (利用格子坐标得到格子编号)
gID := y*gridCount + x
manager.grids[gID] = NewGrid(gID)
}
}
return manager
}
func (g *GridManger) gridWidth() int {
return g.AreaWidth / g.GridCount
}
// GetGIDByPos 通过横纵坐标获取对应的格子ID
func (g *GridManger) GetGIDByPos(entity *Entity) int {
gx := (int(entity.X) - g.StartX) / g.gridWidth()
gy := (int(entity.Y) - g.StartY) / g.gridWidth()
return gy*g.GridCount + gx
}
// GetSurroundGrids 根据格子的gID得到当前周边的九宫格信息
func (g *GridManger) GetSurroundGrids(gID int) (grids []*Grid) {
if _, ok := g.grids[gID]; !ok {
return
}
grids = append(grids, g.grids[gID])
// 根据gID, 得到格子所在的坐标
x, y := gID%g.GridCount, gID/g.GridCount
// 分别将这8个方向的方向向量按顺序写入x, y的分量数组
dx := []int{-1, -1, -1, 0, 0, 1, 1, 1}
dy := []int{-1, 0, 1, -1, 1, -1, 0, 1}
surroundGID := make([]int, 0)
for i := 0; i < 8; i++ {
newX := x + dx[i]
newY := y + dy[i]
if newX >= 0 && newX < g.GridCount && newY >= 0 && newY < g.GridCount {
surroundGID = append(surroundGID, newY*g.GridCount+newX)
}
}
for _, gID := range surroundGID {
grids = append(grids, g.grids[gID])
}
return
}
func (g *GridManger) Add(entity *Entity) {
ID := g.GetGIDByPos(entity)
grid := g.grids[ID]
grid.Entities.Store(entity.Name, entity)
}
func (g *GridManger) Delete(entity *Entity) {
ID := g.GetGIDByPos(entity)
grid := g.grids[ID]
grid.Entities.Delete(entity.Name)
}
func (g *GridManger) Search(entity *Entity) (result []*Entity) {
ID := g.GetGIDByPos(entity)
grids := g.GetSurroundGrids(ID)
for _, grid := range grids {
grid.Entities.Range(func(_, value interface{}) bool {
result = append(result, value.(*Entity))
return true
})
}
return
}

166
grid_test.go Normal file
View File

@@ -0,0 +1,166 @@
package aoi
import (
"fmt"
"github.com/stretchr/testify/assert"
"math/rand"
"sort"
"sync"
"testing"
"time"
)
/**
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)
tests := []struct {
entity *Entity
want []int
}{
{
entity: &Entity{
X: 0, Y: 0,
},
want: []int{0, 1, 5, 6},
},
{
entity: &Entity{
X: 150, Y: 0,
},
want: []int{2, 3, 4, 7, 8, 9},
},
{
entity: &Entity{
X: 50, Y: 50,
},
want: []int{0, 1, 2, 5, 6, 7, 10, 11, 12},
},
{
entity: &Entity{
X: 200, Y: 100,
},
want: []int{8, 9, 13, 14, 18, 19},
},
{
entity: &Entity{
X: 200, Y: 200,
},
want: []int{18, 19, 23, 24},
},
}
for _, tt := range tests {
ID := manger.GetGIDByPos(tt.entity)
grids := manger.GetSurroundGrids(ID)
gID := make([]int, 0)
for _, grid := range grids {
gID = append(gID, grid.GID)
}
sort.Ints(gID)
assert.Equal(t, tt.want, gID)
}
}
func TestNewGridManger(t *testing.T) {
aol := NewGridManger(0, 0, 250, 5)
manger := aol.(*GridManger)
entities := []*Entity{
{
X: 0, Y: 0, Name: "a",
},
{
X: 50, Y: 0, Name: "b",
},
{
X: 100, Y: 0, Name: "c",
},
{
X: 50, Y: 0, Name: "d",
},
{
X: 50, Y: 50, Name: "e",
},
{
X: 50, Y: 100, Name: "f",
},
{
X: 100, Y: 0, Name: "g",
},
{
X: 100, Y: 50, Name: "h",
},
{
X: 100, Y: 100, Name: "i",
},
}
for _, entity := range entities {
manger.Add(entity)
}
search := manger.Search(&Entity{X: 50, Y: 50})
result := make([]string, 0)
for _, entity := range search {
result = append(result, entity.Name)
}
sort.Strings(result)
assert.Equal(t, []string{"a", "b", "c", "d", "e", "f", "g", "h", "i"}, result)
manger.Delete(&Entity{X: 100, Y: 100, Name: "i"})
search2 := manger.Search(&Entity{X: 50, Y: 50})
result2 := make([]string, 0)
for _, entity := range search2 {
result2 = append(result2, entity.Name)
}
sort.Strings(result2)
assert.Equal(t, []string{"a", "b", "c", "d", "e", "f", "g", "h"}, result2)
}
func BenchmarkGridManger(b *testing.B) {
var wg sync.WaitGroup
aol := NewGridManger(0, 0, 250, 5)
manger := aol.(*GridManger)
rand.Seed(time.Now().UnixNano())
for i := 0; i < b.N; i++ {
wg.Add(3000)
for j := 0; j < 1000; j++ {
go func() {
manger.Add(&Entity{
X: float64(rand.Intn(5) * 10),
Y: float64(rand.Intn(5) * 10),
Name: fmt.Sprintf("name%d", rand.Intn(50)),
})
wg.Done()
}()
go func() {
manger.Delete(&Entity{
X: float64(rand.Intn(5) * 10),
Y: float64(rand.Intn(5) * 10),
Name: fmt.Sprintf("name%d", rand.Intn(50)),
})
wg.Done()
}()
go func() {
manger.Search(&Entity{
X: float64(rand.Intn(5) * 10),
Y: float64(rand.Intn(5) * 10),
})
wg.Done()
}()
}
wg.Wait()
}
}

37
quadtree.go Normal file
View File

@@ -0,0 +1,37 @@
package aoi
import "sync"
const (
leftUp int = iota
rightUp
leftDown
rightDown
)
type Node struct {
AreaWidth int // 格子宽度(长=宽)
XStart int // 起始范围
YStart int // 起始范围
Deep int // 深度
Leaf bool // 是否为叶子节点
Parent *Node // 父节点
Child [4]*Node // 子节点
Entities sync.Map // 实体
}
type QuadTree struct {
Root *Node
}
func (q QuadTree) Add(entity *Entity) {
panic("implement me")
}
func (q QuadTree) Delete(entity *Entity) {
panic("implement me")
}
func (q QuadTree) Search(entity *Entity) (result []*Entity) {
panic("implement me")
}

1
quadtree_test.go Normal file
View File

@@ -0,0 +1 @@
package aoi