redesign of quadtree
This commit is contained in:
parent
a853197b61
commit
49c478af5d
217
quadtree.go
217
quadtree.go
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue