362 lines
9.4 KiB
Go
362 lines
9.4 KiB
Go
package aoi
|
|
|
|
import (
|
|
"github.com/fogleman/gg"
|
|
"log"
|
|
"math/rand"
|
|
"rocommon/util"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
//四叉树碰撞检测
|
|
var dc = gg.NewContext(800, 800)
|
|
var MaxObjNum int32 = 2
|
|
|
|
type QuadBounds struct {
|
|
X float32
|
|
Y float32
|
|
Width float32
|
|
Height float32
|
|
}
|
|
type QuadTreeNode struct {
|
|
MaxObjNum int32 //split之前最大能存储的节点数量
|
|
MaxLevels int32 //最大深度
|
|
Level int32 //当前深度
|
|
|
|
Rect *QuadBounds //当前节点的bound
|
|
ObjList []*AoiObject //当前节点存储的对象数量
|
|
Nodes []*QuadTreeNode //子节点
|
|
}
|
|
|
|
func newQuadTreeNode(level int32, maxLevel int32, rect *QuadBounds) *QuadTreeNode {
|
|
node := &QuadTreeNode{
|
|
MaxLevels: maxLevel,
|
|
Level: level,
|
|
Rect: rect,
|
|
MaxObjNum: MaxObjNum,
|
|
}
|
|
return node
|
|
}
|
|
|
|
func NewQuadTree(maxLevel int32, rect *QuadBounds) *QuadTreeNode {
|
|
root := &QuadTreeNode{
|
|
MaxLevels: maxLevel,
|
|
Rect: rect,
|
|
MaxObjNum: MaxObjNum,
|
|
}
|
|
|
|
return root
|
|
}
|
|
|
|
func (this *QuadTreeNode) Clear() {
|
|
this.ObjList = []*AoiObject{}
|
|
for idx := 0; idx < len(this.Nodes); idx++ {
|
|
this.Nodes[idx].Clear()
|
|
}
|
|
this.Nodes = []*QuadTreeNode{}
|
|
this.Level = 0
|
|
}
|
|
|
|
//split当前节点成4个子节点
|
|
func (this *QuadTreeNode) Split() {
|
|
subWidth := this.Rect.Width * 0.5
|
|
subHeight := this.Rect.Height * 0.5
|
|
x := this.Rect.X
|
|
y := this.Rect.Y
|
|
|
|
//-------
|
|
//|2 | 3| top
|
|
//-------
|
|
//|1 | 0| bottom
|
|
//-------
|
|
//^
|
|
//|(y)
|
|
//|
|
|
//|
|
|
//|——————————>(x)
|
|
this.Nodes = append(this.Nodes, newQuadTreeNode(this.Level+1, this.MaxLevels,
|
|
&QuadBounds{X: x + subWidth, Y: y, Width: subWidth, Height: subHeight}))
|
|
this.Nodes = append(this.Nodes, newQuadTreeNode(this.Level+1, this.MaxLevels,
|
|
&QuadBounds{X: x, Y: y, Width: subWidth, Height: subHeight}))
|
|
this.Nodes = append(this.Nodes, newQuadTreeNode(this.Level+1, this.MaxLevels,
|
|
&QuadBounds{X: x, Y: y + subHeight, Width: subWidth, Height: subHeight}))
|
|
this.Nodes = append(this.Nodes, newQuadTreeNode(this.Level+1, this.MaxLevels,
|
|
&QuadBounds{X: x + subWidth, Y: y + subWidth, Width: subWidth, Height: subHeight}))
|
|
|
|
//dc.SetLineWidth(float64(0.2))
|
|
//dc.DrawRectangle(float64(x+subWidth), float64(y), float64(subWidth), float64(subHeight))
|
|
//dc.DrawRectangle(float64(x), float64(y), float64(subWidth), float64(subHeight))
|
|
//dc.DrawRectangle(float64(x), float64(y+subHeight), float64(subWidth), float64(subHeight))
|
|
//dc.DrawRectangle(float64(x+subWidth), float64(y+subHeight), float64(subWidth), float64(subHeight))
|
|
//dc.Stroke()
|
|
}
|
|
|
|
//获取包含rect的节点序号0-3
|
|
func (this *QuadTreeNode) GetIndexes(rect *QuadBounds) []int32 {
|
|
var indexList []int32
|
|
verticalMidPoint := this.Rect.X + this.Rect.Width*0.5
|
|
horizonMidPoint := this.Rect.Y + this.Rect.Height*0.5
|
|
|
|
//判断上下边界
|
|
topQuadrant := rect.Y >= horizonMidPoint
|
|
bottomQuadrant := rect.Y-rect.Height <= horizonMidPoint
|
|
topAndBottomQuadrant := rect.Y+rect.Height >= horizonMidPoint && rect.Y <= horizonMidPoint
|
|
|
|
if topAndBottomQuadrant {
|
|
bottomQuadrant = false
|
|
topQuadrant = false
|
|
}
|
|
//判断是否同时在左右两边
|
|
if rect.X+rect.Width >= verticalMidPoint && rect.X <= verticalMidPoint {
|
|
if topQuadrant {
|
|
indexList = append(indexList, 2, 3)
|
|
} else if bottomQuadrant {
|
|
indexList = append(indexList, 0, 1)
|
|
} else if topAndBottomQuadrant {
|
|
indexList = append(indexList, 0, 1, 2, 3)
|
|
}
|
|
} else if rect.X >= verticalMidPoint {
|
|
//判断只在右边
|
|
if topQuadrant {
|
|
indexList = append(indexList, 3)
|
|
} else if bottomQuadrant {
|
|
indexList = append(indexList, 0)
|
|
} else if topAndBottomQuadrant {
|
|
indexList = append(indexList, 0, 3)
|
|
}
|
|
|
|
} else if rect.X+rect.Width <= verticalMidPoint {
|
|
//判断只在左边
|
|
if topQuadrant {
|
|
indexList = append(indexList, 2)
|
|
} else if bottomQuadrant {
|
|
indexList = append(indexList, 1)
|
|
} else if topAndBottomQuadrant {
|
|
indexList = append(indexList, 1, 2)
|
|
}
|
|
}
|
|
return indexList
|
|
}
|
|
|
|
func (this *QuadTreeNode) Insert(obj *AoiObject) {
|
|
if len(this.Nodes) > 0 {
|
|
indexList := this.GetIndexes(obj.rect)
|
|
if len(indexList) > 0 {
|
|
for idx := 0; idx < len(indexList); idx++ {
|
|
this.Nodes[indexList[idx]].Insert(obj)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
this.ObjList = append(this.ObjList, obj)
|
|
if len(this.ObjList) > int(this.MaxObjNum) && this.Level < this.MaxLevels {
|
|
//split
|
|
if len(this.Nodes) <= 0 {
|
|
this.Split()
|
|
}
|
|
|
|
idx := 0
|
|
for idx < len(this.ObjList) {
|
|
indexList := this.GetIndexes(this.ObjList[idx].rect)
|
|
if len(indexList) > 0 {
|
|
for k := 0; k < len(indexList); k++ {
|
|
this.Nodes[indexList[k]].Insert(this.ObjList[idx])
|
|
}
|
|
this.ObjList = append(this.ObjList[:idx], this.ObjList[idx+1:]...)
|
|
} else {
|
|
idx++
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (this *QuadTreeNode) Remove(obj *AoiObject) {
|
|
indexList := this.GetIndexes(obj.rect)
|
|
if len(this.Nodes) > 0 {
|
|
for idx := 0; idx < len(indexList); idx++ {
|
|
this.Nodes[indexList[idx]].Remove(obj)
|
|
}
|
|
//for idx := 0; idx < len(this.Nodes); idx++ {
|
|
// if len(this.Nodes[idx].ObjList) > 0 {
|
|
// return
|
|
// }
|
|
//}
|
|
//this.Nodes = nil
|
|
} else {
|
|
for idx := 0; idx < len(this.ObjList); idx++ {
|
|
if this.ObjList[idx].Id == obj.Id {
|
|
this.ObjList = append(this.ObjList[:idx], this.ObjList[idx+1:]...)
|
|
break
|
|
}
|
|
}
|
|
if len(this.ObjList) <= 0 {
|
|
this.Nodes = nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func (this *QuadTreeNode) Retrieve(obj *AoiObject, ObjList *[]*AoiObject) {
|
|
indexList := this.GetIndexes(obj.rect)
|
|
|
|
if len(this.ObjList) > 0 {
|
|
*ObjList = append(*ObjList, this.ObjList...)
|
|
}
|
|
if len(this.Nodes) > 0 {
|
|
if len(indexList) > 0 {
|
|
for idx := 0; idx < len(indexList); idx++ {
|
|
this.Nodes[indexList[idx]].Retrieve(obj, ObjList)
|
|
}
|
|
} else {
|
|
for idx := 0; idx < len(this.Nodes); idx++ {
|
|
this.Nodes[idx].Retrieve(obj, ObjList)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestQuadtreePng() {
|
|
rand.Seed(int64(util.GetTimeMilliseconds()))
|
|
dc.SetRGB(0, 0, 0)
|
|
dc.Clear()
|
|
|
|
r := rand.Float64()
|
|
g := rand.Float64()
|
|
b := rand.Float64()
|
|
ww := 0.2
|
|
dc.SetRGBA(r, g, b, float64(1))
|
|
dc.SetLineWidth(float64(ww))
|
|
|
|
quadTest := NewQuadTree(10, &QuadBounds{0, 0, 800, 800})
|
|
|
|
var allObjList []*AoiObject
|
|
//nowTime := time.Now()
|
|
var grid float32 = 10.0
|
|
gridh := quadTest.Rect.Width / grid
|
|
gridv := quadTest.Rect.Height / grid
|
|
for idx := 0; idx < 100; idx++ {
|
|
x := float32(rand.Int31n(int32(gridh))) * grid
|
|
y := float32(rand.Int31n(int32(gridv))) * grid
|
|
w := float32(rand.Intn(4)+1) * grid
|
|
h := float32(rand.Intn(4)+1) * grid
|
|
//w := 1
|
|
//h := 1
|
|
obj := &AoiObject{
|
|
rect: &QuadBounds{x, y, float32(w), float32(h)},
|
|
}
|
|
|
|
//a := rand.Float64()*0.5 + 0.5
|
|
ww := 1
|
|
dc.SetRGBA(1, 1, 1, float64(100))
|
|
dc.SetLineWidth(float64(ww))
|
|
dc.DrawRectangle(float64(x), float64(y), float64(w), float64(h))
|
|
dc.Stroke()
|
|
|
|
quadTest.Insert(obj)
|
|
|
|
allObjList = append(allObjList, obj)
|
|
}
|
|
//log.Printf("time=%v", time.Now().Sub(nowTime).String())
|
|
|
|
for ii := 0; ii < 50; ii++ {
|
|
dc.SetRGB(0, 0, 0)
|
|
dc.Clear()
|
|
var returnObjList []*AoiObject
|
|
//nowTime = time.Now()
|
|
//for idx := 0; idx < len(allObjList); idx++ {
|
|
// quadTest.Retrieve(allObjList[idx], returnObjList)
|
|
//}
|
|
|
|
r = rand.Float64()
|
|
g = rand.Float64()
|
|
b = rand.Float64()
|
|
returnObjList = returnObjList[:0]
|
|
tmpObj := &AoiObject{
|
|
rect: &QuadBounds{
|
|
X: float32(r)*200 + float32(ii)*5,
|
|
Y: float32(g)*200 + float32(ii)*5,
|
|
Width: 400,
|
|
Height: 400,
|
|
}}
|
|
nowTime := time.Now()
|
|
quadTest.Retrieve(tmpObj, &returnObjList)
|
|
log.Printf("time=%v", time.Now().Sub(nowTime).String())
|
|
x := tmpObj.rect.X
|
|
y := tmpObj.rect.Y
|
|
w := tmpObj.rect.Width
|
|
h := tmpObj.rect.Height
|
|
dc.SetRGBA(r+22, g, b, float64(10))
|
|
dc.SetLineWidth(float64(2))
|
|
dc.DrawRectangle(float64(x), float64(y), float64(w), float64(h))
|
|
dc.Stroke()
|
|
|
|
r = rand.Float64()
|
|
g = rand.Float64()
|
|
b = rand.Float64()
|
|
for idx := 0; idx < len(returnObjList); idx++ {
|
|
x := returnObjList[idx].rect.X
|
|
y := returnObjList[idx].rect.Y
|
|
w := returnObjList[idx].rect.Width
|
|
h := returnObjList[idx].rect.Height
|
|
ww := 1
|
|
dc.SetRGB(r+100, g+100, b+100)
|
|
dc.SetLineWidth(float64(ww))
|
|
dc.DrawRectangle(float64(x), float64(y), float64(w), float64(h))
|
|
dc.Stroke()
|
|
}
|
|
//log.Printf("time1=%v", time.Now().Sub(nowTime).String())
|
|
dc.SavePNG("rect" + strconv.Itoa(ii) + ".png")
|
|
}
|
|
}
|
|
|
|
func TestQuadtree() {
|
|
rand.Seed(int64(util.GetTimeMilliseconds()))
|
|
quadTest := NewQuadTree(8, &QuadBounds{0, 0, 800, 800})
|
|
|
|
var allObjList []*AoiObject
|
|
nowTime := time.Now()
|
|
var totalTime time.Duration
|
|
var grid float32 = 10.0
|
|
gridh := quadTest.Rect.Width / grid
|
|
gridv := quadTest.Rect.Height / grid
|
|
for idx := 0; idx < 10000; idx++ {
|
|
x := float32(rand.Int31n(int32(gridh))) * grid
|
|
y := float32(rand.Int31n(int32(gridv))) * grid
|
|
//w := float32(rand.Intn(4)+1) * grid
|
|
//h := float32(rand.Intn(4)+1) * grid
|
|
w := 1
|
|
h := 1
|
|
obj := &AoiObject{
|
|
Id: uint64(idx),
|
|
rect: &QuadBounds{x, y, float32(w), float32(h)},
|
|
}
|
|
|
|
nowTime1 := time.Now()
|
|
quadTest.Insert(obj)
|
|
totalTime += time.Now().Sub(nowTime1)
|
|
|
|
allObjList = append(allObjList, obj)
|
|
}
|
|
log.Printf("time=%v %v", time.Now().Sub(nowTime).String(), totalTime)
|
|
|
|
var retrieveTime time.Duration
|
|
for ii := 0; ii < len(allObjList); ii++ {
|
|
var returnObjList []*AoiObject
|
|
|
|
returnObjList = returnObjList[:0]
|
|
nowTime := time.Now()
|
|
quadTest.Retrieve(allObjList[ii], &returnObjList)
|
|
retrieveTime += time.Now().Sub(nowTime)
|
|
}
|
|
log.Printf("retrieveTime=%v", retrieveTime.String())
|
|
|
|
retrieveTime = 0
|
|
nowTime = time.Now()
|
|
for idx := 0; idx < len(allObjList); idx++ {
|
|
quadTest.Remove(allObjList[idx])
|
|
}
|
|
retrieveTime += time.Now().Sub(nowTime)
|
|
|
|
log.Printf("retrieveTime=%v", retrieveTime.String())
|
|
}
|