180 lines
4.0 KiB
Go
180 lines
4.0 KiB
Go
package world
|
|
|
|
import (
|
|
"a301_game_server/internal/entity"
|
|
"a301_game_server/pkg/mathutil"
|
|
)
|
|
|
|
// cellKey uniquely identifies a grid cell.
|
|
type cellKey struct {
|
|
cx, cz int
|
|
}
|
|
|
|
// GridAOI implements AOI using a spatial grid. Entities in nearby cells are considered visible.
|
|
type GridAOI struct {
|
|
cellSize float32
|
|
viewRange int
|
|
cells map[cellKey]map[uint64]entity.Entity
|
|
entityCell map[uint64]cellKey
|
|
}
|
|
|
|
func NewGridAOI(cellSize float32, viewRange int) *GridAOI {
|
|
return &GridAOI{
|
|
cellSize: cellSize,
|
|
viewRange: viewRange,
|
|
cells: make(map[cellKey]map[uint64]entity.Entity),
|
|
entityCell: make(map[uint64]cellKey),
|
|
}
|
|
}
|
|
|
|
func (g *GridAOI) posToCell(pos mathutil.Vec3) cellKey {
|
|
cx := int(pos.X / g.cellSize)
|
|
cz := int(pos.Z / g.cellSize)
|
|
if pos.X < 0 {
|
|
cx--
|
|
}
|
|
if pos.Z < 0 {
|
|
cz--
|
|
}
|
|
return cellKey{cx, cz}
|
|
}
|
|
|
|
func (g *GridAOI) Add(ent entity.Entity) {
|
|
cell := g.posToCell(ent.Position())
|
|
g.addToCell(cell, ent)
|
|
g.entityCell[ent.EntityID()] = cell
|
|
}
|
|
|
|
func (g *GridAOI) Remove(ent entity.Entity) []AOIEvent {
|
|
eid := ent.EntityID()
|
|
cell, ok := g.entityCell[eid]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
nearby := g.getNearbyFromCell(cell, eid)
|
|
events := make([]AOIEvent, 0, len(nearby))
|
|
for _, observer := range nearby {
|
|
events = append(events, AOIEvent{Observer: observer, Target: ent, Type: AOILeave})
|
|
}
|
|
|
|
g.removeFromCell(cell, eid)
|
|
delete(g.entityCell, eid)
|
|
return events
|
|
}
|
|
|
|
func (g *GridAOI) UpdatePosition(ent entity.Entity, oldPos, newPos mathutil.Vec3) []AOIEvent {
|
|
eid := ent.EntityID()
|
|
oldCell := g.posToCell(oldPos)
|
|
newCell := g.posToCell(newPos)
|
|
|
|
if oldCell == newCell {
|
|
return nil
|
|
}
|
|
|
|
oldVisible := g.visibleCells(oldCell)
|
|
newVisible := g.visibleCells(newCell)
|
|
|
|
leaving := cellDifference(oldVisible, newVisible)
|
|
entering := cellDifference(newVisible, oldVisible)
|
|
|
|
var events []AOIEvent
|
|
|
|
for _, c := range leaving {
|
|
if cellEntities, ok := g.cells[c]; ok {
|
|
for _, other := range cellEntities {
|
|
if other.EntityID() == eid {
|
|
continue
|
|
}
|
|
events = append(events,
|
|
AOIEvent{Observer: other, Target: ent, Type: AOILeave},
|
|
AOIEvent{Observer: ent, Target: other, Type: AOILeave},
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, c := range entering {
|
|
if cellEntities, ok := g.cells[c]; ok {
|
|
for _, other := range cellEntities {
|
|
if other.EntityID() == eid {
|
|
continue
|
|
}
|
|
events = append(events,
|
|
AOIEvent{Observer: other, Target: ent, Type: AOIEnter},
|
|
AOIEvent{Observer: ent, Target: other, Type: AOIEnter},
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
g.removeFromCell(oldCell, eid)
|
|
g.addToCell(newCell, ent)
|
|
g.entityCell[eid] = newCell
|
|
|
|
return events
|
|
}
|
|
|
|
func (g *GridAOI) GetNearby(ent entity.Entity) []entity.Entity {
|
|
cell, ok := g.entityCell[ent.EntityID()]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
return g.getNearbyFromCell(cell, ent.EntityID())
|
|
}
|
|
|
|
func (g *GridAOI) getNearbyFromCell(cell cellKey, excludeID uint64) []entity.Entity {
|
|
var result []entity.Entity
|
|
for _, c := range g.visibleCells(cell) {
|
|
if cellEntities, ok := g.cells[c]; ok {
|
|
for _, e := range cellEntities {
|
|
if e.EntityID() != excludeID {
|
|
result = append(result, e)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (g *GridAOI) visibleCells(center cellKey) []cellKey {
|
|
size := (2*g.viewRange + 1) * (2*g.viewRange + 1)
|
|
cells := make([]cellKey, 0, size)
|
|
for dx := -g.viewRange; dx <= g.viewRange; dx++ {
|
|
for dz := -g.viewRange; dz <= g.viewRange; dz++ {
|
|
cells = append(cells, cellKey{center.cx + dx, center.cz + dz})
|
|
}
|
|
}
|
|
return cells
|
|
}
|
|
|
|
func (g *GridAOI) addToCell(cell cellKey, ent entity.Entity) {
|
|
if g.cells[cell] == nil {
|
|
g.cells[cell] = make(map[uint64]entity.Entity)
|
|
}
|
|
g.cells[cell][ent.EntityID()] = ent
|
|
}
|
|
|
|
func (g *GridAOI) removeFromCell(cell cellKey, eid uint64) {
|
|
if m, ok := g.cells[cell]; ok {
|
|
delete(m, eid)
|
|
if len(m) == 0 {
|
|
delete(g.cells, cell)
|
|
}
|
|
}
|
|
}
|
|
|
|
func cellDifference(a, b []cellKey) []cellKey {
|
|
set := make(map[cellKey]struct{}, len(b))
|
|
for _, c := range b {
|
|
set[c] = struct{}{}
|
|
}
|
|
var diff []cellKey
|
|
for _, c := range a {
|
|
if _, ok := set[c]; !ok {
|
|
diff = append(diff, c)
|
|
}
|
|
}
|
|
return diff
|
|
}
|