581 lines
14 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package aoi
//"gopkg.in/fatih/set.v0"
import (
"math"
"rocommon/util"
"roserver/baseserver/set"
"sort"
"sync"
"time"
)
const (
LISTTYPE_X = 1
LISTTYPE_Y = 2
FIND_NUM_MAX = 10
)
type AoiVector2 struct {
X float32
Y float32
Z float32
}
//set https://studygolang.com/articles/16224?fr=sidebar
//https://github.com/qq362946/AOI
type aoiInfo struct {
MoveSet set.Interface
MoveOnlySet set.Interface
EnterSet set.Interface
LeaveSet set.Interface
MoveNoNtfSet set.Interface
}
type aoiLinkNode struct {
Next *AoiObject
Pre *AoiObject
}
type AoiObject struct {
Id uint64
Pos *AoiVector2
AoiInfo *aoiInfo
node *Node
updateCount int32
rect *QuadBounds
}
func NewAoiObject(id uint64, x, y float32, z float32) *AoiObject {
obj := new(AoiObject)
obj.Id = id
obj.Pos = &AoiVector2{X: x, Y: y, Z: z}
obj.updateCount = 0
obj.AoiInfo = &aoiInfo{}
obj.AoiInfo.MoveSet = set.New(set.NonThreadSafe)
obj.AoiInfo.MoveOnlySet = set.New(set.NonThreadSafe)
obj.AoiInfo.EnterSet = set.New(set.NonThreadSafe)
obj.AoiInfo.LeaveSet = set.New(set.NonThreadSafe)
obj.AoiInfo.MoveNoNtfSet = set.New(set.NonThreadSafe)
return obj
}
func (this *AoiObject) SetNode(node *Node) {
this.node = node
}
func (this *AoiObject) SetPosition(x, y float32) {
this.Pos.X = x
this.Pos.Y = y
}
func (this *AoiObject) GetMoveSize() int32 {
return int32(this.AoiInfo.MoveSet.Size())
}
///////////////////////
type Aoi struct {
nodes map[uint64]*AoiObject
xNodeList *SkipList
yNodeList *SkipList
AoiArea *AoiVector2
quadTree *QuadTreeNode
}
var newNodePool = sync.Pool{
New: func() interface{} {
node := &Node{Forward: make([]*Node, SKIPLIST_MAXLEVEL)}
node.ValueList = make(map[uint64]*AoiObject)
//v.(*AoiObject).SetNode(node)
//node.addValue(v.(*AoiObject))
return node
},
}
func NewAoi() *Aoi {
aoi := &Aoi{}
aoi.nodes = make(map[uint64]*AoiObject)
aoi.xNodeList = NewSkipList(LISTTYPE_X)
aoi.yNodeList = NewSkipList(LISTTYPE_Y)
aoi.quadTree = NewQuadTree(8, &QuadBounds{-10000, -10000, 10000, 10000})
aoi.AoiArea = &AoiVector2{10, 10, 0}
return aoi
}
func (this *Aoi) GetNode(uid uint64) *AoiObject {
if data, ok := this.nodes[uid]; ok {
return data
}
return nil
}
func (this *Aoi) GetAllNode() map[uint64]*AoiObject {
return this.nodes
}
func (this *Aoi) PrintStackList() {
this.xNodeList.PrintSkipList()
}
/**
新加入AOI
*/
func (this *Aoi) Enter(uid uint64, callback func(*AoiObject), x, y, z float32, forceUpdate bool, isMaster bool) *AoiObject {
if data, ok := this.nodes[uid]; ok {
return data
}
//todo...可以使用pool
obj := NewAoiObject(uid, x, y, z)
this.xNodeList.Insert(obj)
//this.yNodeList.Insert(obj)
this.nodes[uid] = obj
if obj.node == nil {
util.InfoF("enter error") //insert操作失败
}
this.updateWithArea(obj, callback, *this.AoiArea, x, y, forceUpdate, isMaster)
//this.PrintStackList()
return obj
}
var profileTime time.Duration
var nowTime = util.GetCurrentTimeNow()
var profileCount int32 = 0
//更新节点
func (this *Aoi) Update(id uint64, callback func(*AoiObject), area AoiVector2, x, y float32, isMaster bool) (*AoiObject, bool) {
if data, ok := this.nodes[id]; ok {
profileCount++
//nowTime := time.Now()
obj, ret := this.updateWithArea(data, callback, area, x, y, false, isMaster)
//this.PrintStackList()
//return this.updateWithArea(data, area,x ,y)
//profileTime += time.Now().Sub(nowTime)
if util.GetCurrentTimeNow().Sub(nowTime) > time.Second {
nowTime = util.GetCurrentTimeNow()
/*
oobb := this.nodes[6735728018393727041]
log.Println("aoiUpdate[1000]:", oobb.Id,profileTime,
len(oobb.AoiInfo.MoveSet.List()),
len(oobb.AoiInfo.EnterSet.List()),
len(oobb.AoiInfo.LeaveSet.List()),
len(oobb.AoiInfo.MoveOnlySet.List()))
*/
profileCount = 0
profileTime = 0
}
return obj, ret
}
return nil, false
}
//callback 处理必须可见玩家列表(目前只包括情侣)
func (this *Aoi) updateWithArea(obj *AoiObject, callback func(*AoiObject), area AoiVector2, x, y float32, forceUpdate bool, isMaster bool) (*AoiObject, bool) {
//移动到新的位置
this.move(obj, x, y)
//减少更新频率发3次移动包做一次视野更新
//obj.updateCount++
//if obj.updateCount%3 != 0 && !forceUpdate {
// return obj, false
//}
//obj.updateCount = 0
//把AOI节点转换到旧的节点里
tempMoveSet := obj.AoiInfo.MoveSet.Copy()
//obj.AoiInfo.MoveOnlySet = obj.AoiInfo.MoveSet.Copy()
//ghost不做处理
if !isMaster {
return obj, true
}
//查找范围坐标
this.find(obj, area)
if callback != nil {
callback(obj)
}
//差集计算(属于MoveSet不属于MoveOnlySet)
obj.AoiInfo.EnterSet = set.Difference(obj.AoiInfo.MoveSet, tempMoveSet)
//属于MoveSet不属于EnterSet
obj.AoiInfo.MoveOnlySet = set.Difference(obj.AoiInfo.MoveSet, obj.AoiInfo.EnterSet)
obj.AoiInfo.LeaveSet = set.Difference(tempMoveSet, obj.AoiInfo.MoveOnlySet)
/*
//差集计算(属于MoveSet不属于MoveOnlySet)
//obj.AoiInfo.EnterSet = set.Difference(obj.AoiInfo.MoveSet, obj.AoiInfo.MoveOnlySet)
//obj.AoiInfo.LeaveSet = set.Difference(obj.AoiInfo.MoveOnlySet, obj.AoiInfo.MoveSet)
//属于MoveSet不属于EnterSet
obj.AoiInfo.MoveOnlySet = set.Difference(obj.AoiInfo.MoveSet, obj.AoiInfo.EnterSet)
*/
//把自己加入别的玩家的进入列表中,否则别的玩家移动时如果离开你的视野不会发送离开消息
//可以用主动跟新自己的方式来避免
for _, data := range obj.AoiInfo.EnterSet.List() {
otherNode := this.GetNode(data.(uint64))
if otherNode != nil {
if otherNode.GetMoveSize() >= FIND_NUM_MAX {
//obj.AoiInfo.MoveNoNtfSet.Add(otherNode.Id)
otherNode.AoiInfo.MoveNoNtfSet.Add(obj.Id) //用来标记当前玩家的移动不需要发送给otherNode玩家
}
otherNode.AoiInfo.MoveSet.Add(obj.Id)
}
}
return obj, true
}
//更新节点
func (this *Aoi) UpdateNew(id uint64, callback func(*AoiObject), area AoiVector2, x, y float32, isMaster bool) (*AoiObject, bool) {
if data, ok := this.nodes[id]; ok {
profileCount++
//nowTime := time.Now()
obj, ret := this.updateWithAreaNew(data, callback, area, x, y, false, isMaster)
//this.PrintStackList()
//return this.updateWithArea(data, area,x ,y)
//profileTime += time.Now().Sub(nowTime)
if util.GetCurrentTimeNow().Sub(nowTime) > time.Second {
nowTime = util.GetCurrentTimeNow()
/*
oobb := this.nodes[6735728018393727041]
log.Println("aoiUpdate[1000]:", oobb.Id,profileTime,
len(oobb.AoiInfo.MoveSet.List()),
len(oobb.AoiInfo.EnterSet.List()),
len(oobb.AoiInfo.LeaveSet.List()),
len(oobb.AoiInfo.MoveOnlySet.List()))
*/
profileCount = 0
profileTime = 0
}
return obj, ret
}
return nil, false
}
func (this *Aoi) updateWithAreaNew(obj *AoiObject, callback func(*AoiObject), area AoiVector2, x, y float32, forceUpdate bool, isMaster bool) (*AoiObject, bool) {
//移动到新的位置
this.move(obj, x, y)
//减少更新频率发3次移动包做一次视野更新
//obj.updateCount++
//if obj.updateCount%3 != 0 && !forceUpdate {
// return obj, false
//}
//obj.updateCount = 0
//把AOI节点转换到旧的节点里
tempMoveSet := obj.AoiInfo.MoveSet.Copy()
//obj.AoiInfo.MoveOnlySet = obj.AoiInfo.MoveSet.Copy()
//ghost不做处理
if !isMaster {
return obj, true
}
//查找范围坐标
this.find(obj, area)
if callback != nil {
callback(obj)
}
//差集计算(属于MoveSet不属于MoveOnlySet)
obj.AoiInfo.EnterSet = set.Difference(obj.AoiInfo.MoveSet, tempMoveSet)
//属于MoveSet不属于EnterSet
obj.AoiInfo.MoveOnlySet = set.Difference(obj.AoiInfo.MoveSet, obj.AoiInfo.EnterSet)
obj.AoiInfo.LeaveSet = set.Difference(tempMoveSet, obj.AoiInfo.MoveOnlySet)
/*
//差集计算(属于MoveSet不属于MoveOnlySet)
//obj.AoiInfo.EnterSet = set.Difference(obj.AoiInfo.MoveSet, obj.AoiInfo.MoveOnlySet)
//obj.AoiInfo.LeaveSet = set.Difference(obj.AoiInfo.MoveOnlySet, obj.AoiInfo.MoveSet)
//属于MoveSet不属于EnterSet
obj.AoiInfo.MoveOnlySet = set.Difference(obj.AoiInfo.MoveSet, obj.AoiInfo.EnterSet)
*/
//把自己加入别的玩家的进入列表中,否则别的玩家移动时如果离开你的视野不会发送离开消息
//可以用主动跟新自己的方式来避免
//for _, data := range obj.AoiInfo.EnterSet.List() {
// otherNode := this.GetNode(data.(uint64))
// if otherNode != nil {
// if otherNode.GetMoveSize() >= FIND_NUM_MAX {
// //obj.AoiInfo.MoveNoNtfSet.Add(otherNode.Id)
// otherNode.AoiInfo.MoveNoNtfSet.Add(obj.Id) //用来标记当前玩家的移动不需要发送给otherNode玩家
// }
// otherNode.AoiInfo.MoveSet.Add(obj.Id)
// }
//}
return obj, true
}
func (this *Aoi) move(obj *AoiObject, x, y float32) {
//移动x
this.moveX(obj, x)
//移动y
//this.moveY(obj, y)
obj.SetPosition(x, y)
}
func (this *Aoi) moveX(obj *AoiObject, x float32) {
if math.Abs(float64(obj.Pos.X-x)) <= 0 {
return
}
this.xNodeList.RemoveNode(obj)
obj.Pos.X = x
this.xNodeList.Insert(obj)
}
func (this *Aoi) moveY(obj *AoiObject, y float32) {
if math.Abs(float64(obj.Pos.Y-y)) <= 0 {
return
}
this.yNodeList.RemoveNode(obj)
obj.Pos.Y = y
this.yNodeList.Insert(obj)
}
func (this *Aoi) find(obj *AoiObject, area AoiVector2) *AoiObject {
obj.AoiInfo.MoveSet.Clear()
//查找X轴时会考虑Y轴的数值所有这边只需要找X轴即可
this.findX(obj, area)
//this.findY(obj, area)
//当前节点列表(节点上所在的玩家列表)
if len(obj.node.ValueList) > 1 {
for id, _ := range obj.node.ValueList {
if id == obj.Id {
continue
}
if !obj.AoiInfo.MoveSet.Has(id) {
obj.AoiInfo.MoveSet.Add(id)
}
}
}
return obj
}
type aoiDistance struct {
objUid uint64
dis float32
}
func (this *Aoi) findX(obj *AoiObject, area AoiVector2) {
bNextOut := false
bPreOut := false
findNum := FIND_NUM_MAX
//查找过程中扫描个数上限
//processLimitNum := findNum
processLimitNumPre := findNum
processLimitNumNext := findNum
var rangeDisList []*aoiDistance
//向后查找
curNext := obj.node.Forward[0]
//向前查找
curPre := obj.node.Pre
for {
if !bNextOut {
if curNext == nil || curNext.Value == nil {
bNextOut = true
continue
}
if this.getDeltaValue(obj.Pos.X, curNext.Value.Pos.X) > area.X {
bNextOut = true
continue
} else {
if len(curNext.ValueList) > 1 {
for id, tmpObj := range curNext.ValueList {
//todo...后续添加做好友处理
tmpDis := this.distance(obj.Pos, tmpObj.Pos)
rangeDisList = append(rangeDisList, &aoiDistance{objUid: id, dis: tmpDis})
processLimitNumNext--
if processLimitNumNext <= 0 {
break
}
//obj.AoiInfo.MoveSet.Add(id)
//findNum--
//if findNum <= 0 {
// break
//}
}
} else {
dis := this.distance(obj.Pos, curNext.Value.Pos)
if dis <= area.X {
rangeDisList = append(rangeDisList, &aoiDistance{objUid: curNext.Value.Id, dis: dis})
processLimitNumNext--
//obj.AoiInfo.MoveSet.Add(curNext.Value.Id)
//findNum--
}
}
}
curNext = curNext.Forward[0]
if processLimitNumNext <= 0 {
break
}
}
if !bPreOut {
//id==0为header节点
if curPre == nil || curPre.Value == nil || curPre.Value.Id <= 0 {
bPreOut = true
continue
}
if this.getDeltaValue(obj.Pos.X, curPre.Value.Pos.X) > area.X {
bPreOut = true
continue
} else {
if len(curPre.ValueList) > 1 {
for id, tmpObj := range curPre.ValueList {
tmpDis := this.distance(obj.Pos, tmpObj.Pos)
rangeDisList = append(rangeDisList, &aoiDistance{objUid: id, dis: tmpDis})
processLimitNumPre--
if processLimitNumPre <= 0 {
break
}
//obj.AoiInfo.MoveSet.Add(id)
//findNum--
//if findNum <= 0 {
// break
//}
}
} else {
dis := this.distance(obj.Pos, curPre.Value.Pos)
if dis <= area.X {
rangeDisList = append(rangeDisList, &aoiDistance{objUid: curPre.Value.Id, dis: dis})
processLimitNumPre--
//obj.AoiInfo.MoveSet.Add(curPre.Value.Id)
//findNum--
}
}
}
curPre = curPre.Pre
if processLimitNumPre <= 0 {
break
}
}
if bNextOut && bPreOut {
break
}
}
if len(rangeDisList) > 0 {
sort.Slice(rangeDisList, func(i, j int) bool {
if math.Abs(float64(rangeDisList[i].dis-rangeDisList[j].dis)) < 0.0001 {
return rangeDisList[i].objUid < rangeDisList[j].objUid
} else {
return rangeDisList[i].dis < rangeDisList[j].dis
}
})
tmpLen := len(rangeDisList)
if tmpLen > findNum {
tmpLen = findNum
}
for idx := 0; idx < tmpLen; idx++ {
obj.AoiInfo.MoveSet.Add(rangeDisList[idx].objUid)
}
}
//test
//this.PrintStackList()
}
func (this *Aoi) findY(obj *AoiObject, area AoiVector2) {
//向后查找
curNext := obj.node.Forward[0]
for {
if curNext == nil || curNext.Value == nil {
break
}
if this.getDeltaValue(obj.Pos.Y, curNext.Value.Pos.Y) > area.Y {
break
} else if this.getDeltaValue(obj.Pos.X, curNext.Value.Pos.X) <= area.X {
if this.distance(obj.Pos, curNext.Value.Pos) <= area.Y {
for id, _ := range curNext.ValueList {
obj.AoiInfo.MoveSet.Add(id)
}
}
}
curNext = curNext.Forward[0]
}
//向前查找
curPre := obj.node.Pre
for {
if curPre == nil || curPre.Value == nil || curPre.Value.Id <= 0 {
break
}
if this.getDeltaValue(obj.Pos.Y, curPre.Value.Pos.Y) > area.Y {
break
} else if this.getDeltaValue(obj.Pos.X, curPre.Value.Pos.X) <= area.X {
if this.distance(obj.Pos, curPre.Value.Pos) <= area.Y {
for id, _ := range curPre.ValueList {
obj.AoiInfo.MoveSet.Add(id)
}
}
}
curPre = curPre.Pre
}
}
func (this *Aoi) LeaveNode(uid uint64) []interface{} {
node := this.GetNode(uid)
if node == nil {
return nil
}
this.xNodeList.RemoveNode(node)
//this.yNodeList.RemoveNode(node)
delete(this.nodes, uid)
for _, data := range node.AoiInfo.MoveSet.List() {
if otherNode := this.GetNode(data.(uint64)); otherNode != nil {
otherNode.AoiInfo.LeaveSet.Add(uid)
}
}
return node.AoiInfo.MoveSet.List()
}
func (this *Aoi) distance(a, b *AoiVector2) float32 {
deltaX := a.X - b.X
deltaY := a.Y - b.Y
//使用近似距离
//return float32(math.Abs(float64(deltaX)) + math.Abs(float64(deltaY)))
return float32(math.Sqrt(float64(deltaX*deltaX + deltaY*deltaY)))
}
func (this *Aoi) getDeltaValue(f1, f2 float32) float32 {
deltaValue := f1 - f2
if deltaValue <= 0 {
return -deltaValue
}
return deltaValue
}