├── go.mod ├── aoi.go ├── LICENSE ├── README.md ├── aoi_test.go ├── towerAOI.go └── listAOI.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kudoochui/aoi 2 | 3 | go 1.15 4 | -------------------------------------------------------------------------------- /aoi.go: -------------------------------------------------------------------------------- 1 | package aoi 2 | 3 | // Observer will get AOI event 4 | type AOIEvent interface { 5 | // The node is entering 6 | OnEnterAOI(nodes []*NodeAOI) 7 | 8 | // The node is moving 9 | OnUpdateAOI(node *NodeAOI) 10 | 11 | // The node is leaving 12 | OnLeaveAOI(nodes []*NodeAOI) 13 | } 14 | 15 | // AOI actions called by entity of the scene 16 | type AOIAction interface { 17 | // The node is entering aoi 18 | EnterAOI(node *NodeAOI) 19 | 20 | // The node is leaving aoi 21 | LeaveAOI(node *NodeAOI) 22 | 23 | // The node is moving to (destX,destY) 24 | MoveAOI(node *NodeAOI, destX, destY float32) 25 | 26 | // Get the specified _range neighbors 27 | FindNeighbors(node *NodeAOI, _range float32) map[*NodeAOI]struct{} 28 | 29 | // Debug only 30 | Print() 31 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 kuduo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aoi 2 | Area-Of-Interest (AOI) 十字链表法和灯塔法 3 | 4 | ```go 5 | type Stub struct { 6 | aoiNode *NodeAOI 7 | } 8 | 9 | func NewNode(x,y,r float32) *NodeAOI { 10 | s := new(Stub) 11 | s.aoiNode = NewNodeAOI(x,y,r) 12 | s.aoiNode.SetListener(s) 13 | return s.aoiNode 14 | } 15 | 16 | func (s *Stub) OnEnterAOI(nodes []*NodeAOI) { 17 | for _,node := range nodes { 18 | fmt.Printf("(%f,%f) OnEnterAOI: (%f,%f) \n", s.aoiNode.x, s.aoiNode.y, node.x, node.y) 19 | } 20 | } 21 | 22 | func (s *Stub) OnLeaveAOI(nodes []*NodeAOI) { 23 | for _,node := range nodes { 24 | fmt.Printf("(%f,%f) OnLeaveAOI: (%f,%f) \n", s.aoiNode.x, s.aoiNode.y, node.x, node.y) 25 | } 26 | } 27 | 28 | func (s *Stub) OnUpdateAOI(node *NodeAOI) { 29 | 30 | } 31 | 32 | func TestAOI(t *testing.T) { 33 | a := NewAOI() 34 | n1 := NewNode(1,5,2) 35 | n2 := NewNode(6,6,2) 36 | n3 := NewNode(3,1,2) 37 | n4 := NewNode(2,2,2) 38 | n5 := NewNode(5,3,2) 39 | a.EnterAOI(n1) 40 | a.EnterAOI(n2) 41 | a.EnterAOI(n3) 42 | a.EnterAOI(n4) 43 | a.EnterAOI(n5) 44 | 45 | //a.LeaveAOI(n5) 46 | 47 | n6 := NewNode(3,3,2) 48 | a.EnterAOI(n6) 49 | 50 | a.MoveAOI(n6,4,4) 51 | a.Print() 52 | } 53 | ``` -------------------------------------------------------------------------------- /aoi_test.go: -------------------------------------------------------------------------------- 1 | package aoi 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | type Stub struct { 9 | aoiNode *NodeAOI 10 | } 11 | 12 | func NewNode(x,y,r float32) *NodeAOI { 13 | s := new(Stub) 14 | s.aoiNode = NewNodeAOI(x,y,r) 15 | s.aoiNode.SetListener(s) 16 | return s.aoiNode 17 | } 18 | 19 | func (s *Stub) OnEnterAOI(nodes []*NodeAOI) { 20 | for _,node := range nodes { 21 | fmt.Printf("(%f,%f) OnEnterAOI: (%f,%f) \n", s.aoiNode.x, s.aoiNode.y, node.x, node.y) 22 | } 23 | } 24 | 25 | func (s *Stub) OnLeaveAOI(nodes []*NodeAOI) { 26 | for _,node := range nodes { 27 | fmt.Printf("(%f,%f) OnLeaveAOI: (%f,%f) \n", s.aoiNode.x, s.aoiNode.y, node.x, node.y) 28 | } 29 | } 30 | 31 | func (s *Stub) OnUpdateAOI(node *NodeAOI) { 32 | 33 | } 34 | 35 | func TestAOI(t *testing.T) { 36 | a := NewAOI() 37 | n1 := NewNode(1,5,2) 38 | n2 := NewNode(6,6,2) 39 | n3 := NewNode(3,1,2) 40 | n4 := NewNode(2,2,2) 41 | n5 := NewNode(5,3,2) 42 | a.EnterAOI(n1) 43 | a.EnterAOI(n2) 44 | a.EnterAOI(n3) 45 | a.EnterAOI(n4) 46 | a.EnterAOI(n5) 47 | 48 | //a.LeaveAOI(n5) 49 | 50 | n6 := NewNode(3,3,2) 51 | a.EnterAOI(n6) 52 | 53 | a.MoveAOI(n6,4,4) 54 | a.Print() 55 | } -------------------------------------------------------------------------------- /towerAOI.go: -------------------------------------------------------------------------------- 1 | package aoi 2 | 3 | import "fmt" 4 | 5 | // A grid of the map 6 | type tower struct { 7 | objs map[*NodeAOI]struct{} 8 | } 9 | 10 | func (t *tower) init() { 11 | t.objs = map[*NodeAOI]struct{}{} 12 | } 13 | 14 | func (t *tower) addObject(node *NodeAOI) { 15 | t.objs[node] = struct{}{} 16 | } 17 | 18 | func (t *tower) removeObject(node *NodeAOI) { 19 | delete(t.objs, node) 20 | } 21 | 22 | type TowerAOI struct { 23 | minX, maxX, minY, maxY float32 24 | towerRange float32 25 | towers [][]tower 26 | xTowerNum, yTowerNum int 27 | } 28 | 29 | // Create Tower AOI 30 | func NewTowerAOI(minX, maxX, minY, maxY float32, towerRange float32) AOIAction { 31 | aoi := &TowerAOI{ 32 | minX: minX, 33 | maxX: maxX, 34 | minY: minY, 35 | maxY: maxY, 36 | towerRange: towerRange, 37 | } 38 | aoi.xTowerNum = int((maxX - minX)/towerRange) + 1 39 | aoi.yTowerNum = int((maxY - minY)/towerRange) + 1 40 | for i := 0; i < aoi.xTowerNum; i++ { 41 | aoi.towers[i] = make([]tower, aoi.yTowerNum) 42 | for j := 0; j < aoi.yTowerNum; j++ { 43 | aoi.towers[i][j].init() 44 | } 45 | } 46 | return aoi 47 | } 48 | 49 | // The node is entering aoi 50 | func (aoi *TowerAOI) EnterAOI(node *NodeAOI) { 51 | arr := make([]*NodeAOI, 0) 52 | neighbors := aoi.findNeighbors(node) 53 | for obj := range neighbors { 54 | obj.listener.OnEnterAOI([]*NodeAOI{node}) 55 | arr = append(arr, obj) 56 | } 57 | 58 | t := aoi.getTowerByPos(node.x, node.y) 59 | t.addObject(node) 60 | node.listener.OnEnterAOI(arr) 61 | } 62 | 63 | // The node is leaving aoi 64 | func (aoi *TowerAOI) LeaveAOI(node *NodeAOI) { 65 | t := aoi.getTowerByPos(node.x, node.y) 66 | t.removeObject(node) 67 | 68 | arr := make([]*NodeAOI, 0) 69 | neighbors := aoi.findNeighbors(node) 70 | for obj := range neighbors { 71 | obj.listener.OnLeaveAOI([]*NodeAOI{node}) 72 | arr = append(arr, obj) 73 | } 74 | node.listener.OnLeaveAOI(arr) 75 | } 76 | 77 | // The node is moving to (destX,destY) 78 | func (aoi *TowerAOI) MoveAOI(node *NodeAOI, destX, destY float32) { 79 | oldNeighbors := aoi.findNeighbors(node) 80 | t := aoi.getTowerByPos(node.x, node.y) 81 | t.removeObject(node) 82 | node.x = destX 83 | node.y = destY 84 | t = aoi.getTowerByPos(node.x, node.y) 85 | t.addObject(node) 86 | newNeighbors := aoi.findNeighbors(node) 87 | arr := make([]*NodeAOI, 0) 88 | for old,_ := range oldNeighbors { 89 | if _,ok := newNeighbors[old]; !ok { 90 | old.listener.OnLeaveAOI([]*NodeAOI{node}) 91 | arr = append(arr, old) 92 | } 93 | } 94 | node.listener.OnLeaveAOI(arr) 95 | arr = make([]*NodeAOI, 0) 96 | for _new,_ := range newNeighbors { 97 | if _,ok := oldNeighbors[_new]; !ok { 98 | _new.listener.OnEnterAOI([]*NodeAOI{node}) 99 | arr = append(arr, _new) 100 | } else { 101 | _new.listener.OnUpdateAOI(node) 102 | } 103 | } 104 | node.listener.OnEnterAOI(arr) 105 | } 106 | 107 | // Get the specified _range neighbors 108 | func (aoi *TowerAOI) FindNeighbors(node *NodeAOI, _range float32) map[*NodeAOI]struct{} { 109 | neighbors := make(map[*NodeAOI]struct{}, 0) 110 | 111 | xiMin, yiMin := aoi.transPosToTowerCoord(node.x - _range, node.y - _range) 112 | xiMax, yiMax := aoi.transPosToTowerCoord(node.x + _range, node.y + _range) 113 | for xi := xiMin; xi <= xiMax; xi++ { 114 | for yi := yiMin; yi <= yiMax; yi++ { 115 | t := &aoi.towers[xi][yi] 116 | for obj := range t.objs { 117 | neighbors[obj] = struct{}{} 118 | } 119 | } 120 | } 121 | 122 | return neighbors 123 | } 124 | 125 | // Debug only 126 | func (aoi *TowerAOI) Print() { 127 | for x := 0; x < aoi.xTowerNum; x++ { 128 | for y := 0; y < aoi.yTowerNum; y++ { 129 | t := aoi.towers[x][y] 130 | for cur := range t.objs { 131 | fmt.Printf("(%f,%f)->",cur.x,cur.y) 132 | } 133 | } 134 | } 135 | } 136 | 137 | // Transfer position of world coordinate to tower coordinate. 138 | func (aoi *TowerAOI) transPosToTowerCoord(x,y float32) (int, int) { 139 | xi := int((x - aoi.minX) / aoi.towerRange) 140 | yi := int((y - aoi.minY) / aoi.towerRange) 141 | 142 | if xi < 0 { 143 | xi = 0 144 | } else if xi >= aoi.xTowerNum { 145 | xi = aoi.xTowerNum - 1 146 | } 147 | 148 | if yi < 0 { 149 | yi = 0 150 | } else if yi >= aoi.yTowerNum { 151 | yi = aoi.yTowerNum - 1 152 | } 153 | return xi, yi 154 | } 155 | 156 | func (aoi *TowerAOI) getTowerByPos(x,y float32) *tower { 157 | xi, yi := aoi.transPosToTowerCoord(x, y) 158 | return &aoi.towers[xi][yi] 159 | } 160 | 161 | func (aoi *TowerAOI) findNeighbors(node *NodeAOI) map[*NodeAOI]struct{} { 162 | neighbors := make(map[*NodeAOI]struct{}, 0) 163 | 164 | xiMin, yiMin := aoi.transPosToTowerCoord(node.x - node.r, node.y - node.r) 165 | xiMax, yiMax := aoi.transPosToTowerCoord(node.x + node.r, node.y + node.r) 166 | for xi := xiMin; xi <= xiMax; xi++ { 167 | for yi := yiMin; yi <= yiMax; yi++ { 168 | t := &aoi.towers[xi][yi] 169 | for obj := range t.objs { 170 | neighbors[obj] = struct{}{} 171 | } 172 | } 173 | } 174 | 175 | return neighbors 176 | } -------------------------------------------------------------------------------- /listAOI.go: -------------------------------------------------------------------------------- 1 | package aoi 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "sync" 7 | ) 8 | 9 | // AOI node 10 | type NodeAOI struct { 11 | xPrev *NodeAOI 12 | xNext *NodeAOI 13 | yPrev *NodeAOI 14 | yNext *NodeAOI 15 | 16 | x float32 17 | y float32 18 | r float32 //range 19 | listener AOIEvent 20 | object interface{} 21 | } 22 | 23 | // Create a node 24 | func NewNodeAOI(x,y float32, r float32) *NodeAOI { 25 | return &NodeAOI{ 26 | x: x, 27 | y: y, 28 | r: r, 29 | } 30 | } 31 | 32 | // Range can be changed anytime 33 | func (n *NodeAOI) SetListener(listener AOIEvent) { 34 | n.listener = listener 35 | } 36 | 37 | func (n *NodeAOI) BindObject(obj interface{}) { 38 | n.object = obj 39 | } 40 | 41 | func (n *NodeAOI) GetObject() interface{} { 42 | return n.object 43 | } 44 | 45 | // Range can be changed anytime 46 | func (n *NodeAOI) SetRange(r float32) { 47 | n.r = r 48 | } 49 | 50 | // AOI List 51 | type ListAOI struct { 52 | xList *NodeAOI 53 | yList *NodeAOI 54 | lock sync.RWMutex 55 | } 56 | 57 | // Create list AOI 58 | func NewAOI() AOIAction { 59 | return new(ListAOI) 60 | } 61 | 62 | // Add a node to list 63 | func (l *ListAOI) add(node *NodeAOI) { 64 | l.lock.Lock() 65 | defer l.lock.Unlock() 66 | 67 | if l.xList == nil || l.yList == nil{ 68 | l.xList, l.yList = node, node 69 | return 70 | } 71 | var tail *NodeAOI 72 | found := false 73 | for cur := l.xList; cur != nil; cur = cur.xNext { 74 | if cur.x > node.x { 75 | node.xNext = cur 76 | if cur.xPrev != nil{ 77 | node.xPrev = cur.xPrev 78 | cur.xPrev.xNext = node 79 | } else { 80 | l.xList = node 81 | } 82 | cur.xPrev = node 83 | found = true 84 | break 85 | } 86 | tail = cur 87 | } 88 | if tail != nil && !found{ 89 | tail.xNext = node 90 | node.xPrev = tail 91 | } 92 | 93 | tail = nil 94 | found = false 95 | for cur := l.yList; cur != nil; cur = cur.yNext { 96 | if cur.y > node.y { 97 | node.yNext = cur 98 | if cur.yPrev != nil{ 99 | node.yPrev = cur.yPrev 100 | cur.yPrev.yNext = node 101 | } else { 102 | l.yList = node 103 | } 104 | cur.yPrev = node 105 | found = true 106 | break 107 | } 108 | tail = cur 109 | } 110 | if tail != nil && !found{ 111 | tail.yNext = node 112 | node.yPrev = tail 113 | } 114 | } 115 | 116 | // Remove the node from list 117 | func (l *ListAOI) remove(node *NodeAOI) { 118 | l.lock.Lock() 119 | defer l.lock.Unlock() 120 | 121 | if node == l.xList { 122 | if node.xNext != nil { 123 | l.xList = node.xNext 124 | if l.xList.xPrev != nil { 125 | l.xList.xPrev = nil 126 | } 127 | } else { 128 | l.xList = nil 129 | } 130 | } else if node.xPrev != nil && node.xNext != nil { 131 | node.xPrev.xNext = node.xNext 132 | node.xNext.xPrev = node.xPrev 133 | } else if node.xPrev != nil { 134 | node.xPrev.xNext = nil 135 | } 136 | 137 | if node == l.yList { 138 | if node.yNext != nil { 139 | l.yList = node.yNext 140 | if l.yList.yPrev != nil { 141 | l.yList.yPrev = nil 142 | } 143 | } else { 144 | l.yList = nil 145 | } 146 | } else if node.yPrev != nil && node.yNext != nil { 147 | node.yPrev.yNext = node.yNext 148 | node.yNext.yPrev = node.yPrev 149 | } else if node.yPrev != nil { 150 | node.yPrev.yNext = nil 151 | } 152 | 153 | node.xPrev = nil 154 | node.xNext = nil 155 | node.yPrev = nil 156 | node.yNext = nil 157 | } 158 | 159 | // Enter event 160 | func (l *ListAOI) EnterAOI(node *NodeAOI) { 161 | l.add(node) 162 | neighbors := l.findNeighbors(node) 163 | arr := make([]*NodeAOI, 0) 164 | for neighbor,_ := range neighbors { 165 | neighbor.listener.OnEnterAOI([]*NodeAOI{node}) 166 | arr = append(arr, neighbor) 167 | } 168 | node.listener.OnEnterAOI(arr) 169 | } 170 | 171 | // Leave event 172 | func (l *ListAOI) LeaveAOI(node *NodeAOI) { 173 | neighbors := l.findNeighbors(node) 174 | arr := make([]*NodeAOI, 0) 175 | for neighbor,_ := range neighbors { 176 | neighbor.listener.OnLeaveAOI([]*NodeAOI{node}) 177 | arr = append(arr, neighbor) 178 | } 179 | node.listener.OnLeaveAOI(arr) 180 | l.remove(node) 181 | } 182 | 183 | // Move event 184 | func (l *ListAOI) MoveAOI(node *NodeAOI, destX, destY float32) { 185 | oldNeighbors := l.findNeighbors(node) 186 | l.remove(node) 187 | node.x = destX 188 | node.y = destY 189 | l.add(node) 190 | newNeighbors := l.findNeighbors(node) 191 | arr := make([]*NodeAOI, 0) 192 | for old,_ := range oldNeighbors { 193 | if _,ok := newNeighbors[old]; !ok { 194 | old.listener.OnLeaveAOI([]*NodeAOI{node}) 195 | arr = append(arr, old) 196 | } 197 | } 198 | node.listener.OnLeaveAOI(arr) 199 | arr = make([]*NodeAOI, 0) 200 | for _new,_ := range newNeighbors { 201 | if _,ok := oldNeighbors[_new]; !ok { 202 | _new.listener.OnEnterAOI([]*NodeAOI{node}) 203 | arr = append(arr, _new) 204 | } else { 205 | _new.listener.OnUpdateAOI(node) 206 | } 207 | } 208 | node.listener.OnEnterAOI(arr) 209 | } 210 | 211 | // Find the watchers(neighbors) 212 | func (l *ListAOI) findNeighbors(node *NodeAOI) map[*NodeAOI]struct{} { 213 | neighbors := make(map[*NodeAOI]struct{}, 0) 214 | 215 | l.lock.RLock() 216 | defer l.lock.RUnlock() 217 | 218 | for cur := node.xNext; cur != nil; cur = cur.xNext{ 219 | if cur.x - node.x > node.r { 220 | break 221 | } else { 222 | if math.Abs(float64(cur.y - node.y)) <= float64(node.r) { 223 | neighbors[cur] = struct{}{} 224 | } 225 | } 226 | } 227 | 228 | for cur := node.xPrev; cur != nil; cur = cur.xPrev{ 229 | if node.x - cur.x > node.r { 230 | break 231 | } else { 232 | if math.Abs(float64(cur.y - node.y)) <= float64(node.r) { 233 | neighbors[cur] = struct{}{} 234 | } 235 | } 236 | } 237 | return neighbors 238 | } 239 | 240 | func (l *ListAOI) FindNeighbors(node *NodeAOI, _range float32) map[*NodeAOI]struct{} { 241 | neighbors := make(map[*NodeAOI]struct{}, 0) 242 | 243 | l.lock.RLock() 244 | defer l.lock.RUnlock() 245 | 246 | for cur := node.xNext; cur != nil; cur = cur.xNext{ 247 | if cur.x - node.x > _range { 248 | break 249 | } else { 250 | if math.Abs(float64(cur.y - node.y)) <= float64(_range) { 251 | neighbors[cur] = struct{}{} 252 | } 253 | } 254 | } 255 | 256 | for cur := node.xPrev; cur != nil; cur = cur.xPrev{ 257 | if node.x - cur.x > _range { 258 | break 259 | } else { 260 | if math.Abs(float64(cur.y - node.y)) <= float64(_range) { 261 | neighbors[cur] = struct{}{} 262 | } 263 | } 264 | } 265 | return neighbors 266 | } 267 | 268 | // Debug only 269 | func (l *ListAOI) Print() { 270 | for cur := l.xList; cur != nil; cur = cur.xNext{ 271 | fmt.Printf("(%f,%f)->",cur.x,cur.y) 272 | } 273 | fmt.Println("x list end.") 274 | for cur := l.yList; cur != nil; cur = cur.yNext{ 275 | fmt.Printf("(%f,%f)->",cur.x,cur.y) 276 | } 277 | fmt.Println("y list end.") 278 | } 279 | --------------------------------------------------------------------------------