redesign of quadtree

This commit is contained in:
unknown 2020-07-12 10:51:58 +02:00
parent a853197b61
commit 49c478af5d
1 changed files with 142 additions and 75 deletions

View File

@ -1,21 +1,25 @@
package pixel package pixel
import (
"github.com/faiface/pixel/imdraw"
)
type Collidable interface { type Collidable interface {
GetRect() Rect GetRect() Rect
} }
// i separated this data so i can simply copy it to subnodes
type Common struct { type Common struct {
Depth int // maximal level tree can reach Depth int
level int // this takes track of level Level int
Cap int // max amount of objects per quadrant, if there is more quadrant splits Cap int //max amount of objects per quadrant, if there is more quadrant splits
} }
type Quadtree struct { type Quadtree struct {
Rect Rect
tl, tr, bl, br, pr *Quadtree
Shapes []Collidable
Common Common
nodes []*Quadtree splitted bool
shapes []Collidable
} }
// Creates new quad tree reference. // Creates new quad tree reference.
@ -26,7 +30,7 @@ type Quadtree struct {
// if shapes cannot fit into smallest quadrants. // if shapes cannot fit into smallest quadrants.
// cap - sets maximal capacity of quadrant before it splits to 4 smaller. Making can too big is // cap - sets maximal capacity of quadrant before it splits to 4 smaller. Making can too big is
// inefficient. optimal value can be 10 but its allways better to test what works the best. // inefficient. optimal value can be 10 but its allways better to test what works the best.
func NewQuadtree(bounds Rect, depth, cap int) *Quadtree { func NewQuadTree(bounds Rect, depth, cap int) *Quadtree {
return &Quadtree{ return &Quadtree{
Rect: bounds, Rect: bounds,
Common: Common{ Common: Common{
@ -36,136 +40,199 @@ func NewQuadtree(bounds Rect, depth, cap int) *Quadtree {
} }
} }
//generates subquadrants // generates subquadrants, always check if quadrant is not already splitted
func (q *Quadtree) split() { func (q *Quadtree) split() {
q.nodes = make([]*Quadtree, 4) q.splitted = true
newCommon := q.Common newCommon := q.Common
newCommon.level++ newCommon.Level++
halfH := q.H() / 2 halfH := q.H() / 2
halfW := q.W() / 2 halfW := q.W() / 2
center := q.Center() center := q.Center()
//top-left q.tl = &Quadtree{
q.nodes[0] = &Quadtree{
Rect: Rect{ Rect: Rect{
Min: V(q.Min.X, q.Min.Y+halfH), Min: V(q.Min.X, q.Min.Y+halfH),
Max: V(q.Max.X-halfW, q.Max.Y), Max: V(q.Max.X-halfW, q.Max.Y),
}, },
pr: q,
Common: newCommon, Common: newCommon,
} }
//top-right q.tr = &Quadtree{
q.nodes[1] = &Quadtree{
Rect: Rect{ Rect: Rect{
Min: center, Min: center,
Max: q.Max, Max: q.Max,
}, },
pr: q,
Common: newCommon, Common: newCommon,
} }
//bottom-left q.bl = &Quadtree{
q.nodes[2] = &Quadtree{
Rect: Rect{ Rect: Rect{
Min: q.Min, Min: q.Min,
Max: center, Max: center,
}, },
pr: q,
Common: newCommon, Common: newCommon,
} }
//bottom-right q.br = &Quadtree{
q.nodes[3] = &Quadtree{
Rect: Rect{ Rect: Rect{
Min: V(q.Min.X+halfW, q.Min.Y), Min: V(q.Min.X+halfW, q.Min.Y),
Max: V(q.Max.X, q.Min.Y+halfH), Max: V(q.Max.X, q.Min.Y+halfH),
}, },
pr: q,
Common: newCommon, Common: newCommon,
} }
} }
// finds out to witch subquadrant the shape belongs to. Shape has to overlap only with one quadrant, func (q *Quadtree) fits(rect Rect) bool {
// otherwise it returns -1 return rect.Max.X > q.Min.X && rect.Max.X < q.Max.X && rect.Min.Y > q.Min.Y && rect.Max.Y < q.Max.Y
func (q *Quadtree) getSub(rect Rect) int8 { }
// finds out in witch subquadrant the shape belongs to. Shape has to overlap only with one quadrant,
// otherwise it returns nil
func (q *Quadtree) getSub(rect Rect) *Quadtree {
vertical := q.Min.X + q.W()/2 vertical := q.Min.X + q.W()/2
horizontal := q.Min.Y + q.H()/2 horizontal := q.Min.Y + q.H()/2
if rect.Max.X < q.Min.X || rect.Max.X > q.Max.X || rect.Min.Y < q.Min.Y || rect.Max.Y > q.Max.Y { if !q.fits(rect) {
return -1 return nil
} }
left := rect.Max.X < vertical left := rect.Max.X <= vertical
right := rect.Min.X > vertical right := rect.Min.X >= vertical
if rect.Min.Y > horizontal { if rect.Min.Y >= horizontal {
// top
if left { if left {
return 0 // left return q.tl
} else if right { } else if right {
return 1 // right return q.tr
} }
} else if rect.Max.Y < horizontal { } else if rect.Max.Y <= horizontal {
// bottom
if left { if left {
return 2 // left return q.bl
} else if right { } else if right {
return 3 // right return q.br
} }
} }
return -1 return nil
} }
// Adds the shape to quad tree and asians it to correct quadrant. // Adds the shape to quad tree and asians it to correct quadrant.
// Proper way is adding all shapes first and then detecting collisions. // Proper way is adding all shapes first and then detecting collisions.
// For struct to implement Collidable interface it has to have // For struct to implement Collidable interface it has to implement
// GetRect() *pixel.Rect defined. GetRect function also slightly affects performance. // GetRect() *pixel.Rect. GetRect function also slightly affects performance.
func (q *Quadtree) Insert(collidable Collidable) { func (q *Quadtree) Insert(collidable Collidable) {
rect := collidable.GetRect() rect := collidable.GetRect()
// this is little memory expensive but it makes acesing shapes faster
q.shapes = append(q.shapes, collidable) if q.splitted {
if len(q.nodes) != 0 { fitting := q.getSub(rect)
i := q.getSub(rect) if fitting != nil {
if i != -1 { fitting.Insert(collidable)
q.nodes[i].Insert(collidable) return
} }
q.Shapes = append(q.Shapes, collidable)
return return
} else if q.Cap == len(q.shapes) && q.level != q.Depth { }
q.Shapes = append(q.Shapes, collidable)
if q.Cap <= len(q.Shapes) && q.Level != q.Depth {
q.split() q.split()
for _, s := range q.shapes { new := []Collidable{}
i := q.getSub(s.GetRect()) for _, s := range q.Shapes {
if i != -1 { fitting := q.getSub(s.GetRect())
q.nodes[i].Insert(s) if fitting != nil {
fitting.Insert(s)
} else {
new = append(new, s)
}
}
q.Shapes = new
}
}
// pushes shape to parrent until it fits him
func (q *Quadtree) withdraw(c Collidable) {
if q.pr == nil || q.fits(c.GetRect()) {
q.Shapes = append(q.Shapes, c)
} else {
q.pr.withdraw(c)
}
}
// reassigns shapes to quadrants if needed
func (q *Quadtree) Update() {
new := []Collidable{}
if len(q.Shapes) > q.Cap && !q.splitted {
q.split()
}
if q.splitted {
q.tl.Update()
q.tr.Update()
q.bl.Update()
q.br.Update()
for _, c := range q.Shapes {
rect := c.GetRect()
sub := q.getSub(rect)
if sub != nil {
sub.Shapes = append(sub.Shapes, c)
} else if q.fits(rect) || q.pr == nil {
new = append(new, c)
} else {
q.pr.withdraw(c)
}
}
} else {
for _, c := range q.Shapes {
if q.fits(c.GetRect()) || q.pr == nil {
new = append(new, c)
} else {
q.pr.withdraw(c)
} }
} }
} }
q.Shapes = new
} }
// gets smallest generated quadrant that rect fits into // returns all coliding collidables, if rect belongs to object that is already
func (q *Quadtree) getQuad(rect Rect) *Quadtree { // inserted in tree it returns is as well
if len(q.nodes) == 0 { func (q *Quadtree) GetColliding(rect Rect, con *[]Collidable) {
return q if q.splitted {
} if q.tl.Intersects(rect) {
subIdx := q.getSub(rect) q.tl.GetColliding(rect, con)
if subIdx == -1 { }
return q if q.tr.Intersects(rect) {
} q.tr.GetColliding(rect, con)
return q.nodes[subIdx].getQuad(rect) }
} if q.bl.Intersects(rect) {
q.bl.GetColliding(rect, con)
// returns all collidables that this rect can possibly collide with }
// thought it also returns the shape it self if it wos inserted if q.br.Intersects(rect) {
func (q *Quadtree) Retrieve(rect Rect) []Collidable { q.br.GetColliding(rect, con)
return q.getQuad(rect).shapes }
} }
for _, c := range q.Shapes {
// returns all coliding shapes if c.GetRect().Intersects(rect) {
func (q *Quadtree) GetColliding(collidable Collidable) []Collidable { *con = append(*con, c)
var res []Collidable
rect := collidable.GetRect()
for _, c := range q.Retrieve(rect) {
if c.GetRect().Intersects(rect) && c != collidable {
res = append(res, c)
} }
} }
return res
} }
// Resets the tree, use this every frame before inserting all shapes // Resets the tree, use this every frame before inserting all shapes
// other wise you will run out of memory eventually and tree will not even work properly // other wise you will run out of memory eventually and tree will not even work properly
func (q *Quadtree) Clear() { func (q *Quadtree) Clear() {
q.shapes = []Collidable{} q.Shapes = []Collidable{}
q.nodes = []*Quadtree{} q.tl, q.tr, q.bl, q.br = nil, nil, nil, nil
q.splitted = false
}
// visualizes state of quadtree
func (q *Quadtree) Draw(id *imdraw.IMDraw, thickness float64) {
id.Push(q.Min)
id.Push(q.Max)
id.Rectangle(thickness)
if !q.splitted {
return
}
q.tl.Draw(id, thickness)
q.tr.Draw(id, thickness)
q.bl.Draw(id, thickness)
q.br.Draw(id, thickness)
} }