go-opengl-pixel/quadtree.go

172 lines
4.2 KiB
Go

package pixel
type Collidable interface {
GetRect() *Rect
}
// i separated this data so i can simply copy it to subnodes
type Common struct {
Depth int // maximal level tree can reach
level int // this takes track of level
Cap int // max amount of objects per quadrant, if there is more quadrant splits
}
type Quadtree struct {
Rect
nodes []*Quadtree
shapes []Collidable
Common
}
// Creates new quad tree reference.
// bounds - defines position of quad tree and its size. If shapes goes out of bounds they
// will not be assigned to quadrants and the tree will be ineffective.
// depth - resolution of quad tree. It lavais splits in half so if bounds size is 100 x 100
// and depth is 2 smallest quadrants will be 25 x 25. Making resolution too high is redundant
// if shapes cannot fit into smallest quadrants.
// 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.
func NewQuadtree(bounds Rect, depth, cap int) *Quadtree {
return &Quadtree{
Rect: bounds,
Common: Common{
Depth: depth,
Cap: cap,
},
}
}
//generates subquadrants
func (q *Quadtree) split() {
q.nodes = make([]*Quadtree, 4)
newCommon := q.Common
newCommon.level++
halfH := q.H() / 2
halfW := q.W() / 2
center := q.Center()
//top-left
q.nodes[0] = &Quadtree{
Rect: Rect{
Min: V(q.Min.X, q.Min.Y+halfH),
Max: V(q.Max.X-halfW, q.Max.Y),
},
Common: newCommon,
}
//top-right
q.nodes[1] = &Quadtree{
Rect: Rect{
Min: center,
Max: q.Max,
},
Common: newCommon,
}
//bottom-left
q.nodes[2] = &Quadtree{
Rect: Rect{
Min: q.Min,
Max: center,
},
Common: newCommon,
}
//bottom-right
q.nodes[3] = &Quadtree{
Rect: Rect{
Min: V(q.Min.X+halfW, q.Min.Y),
Max: V(q.Max.X, q.Min.Y+halfH),
},
Common: newCommon,
}
}
// finds out to witch subquadrant the shape belongs to. Shape has to overlap only with one quadrant,
// otherwise it returns -1
func (q *Quadtree) getSub(rect *Rect) int8 {
vertical := q.Min.X + q.W()/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 {
return -1
}
left := rect.Max.X < vertical
right := rect.Min.X > vertical
if rect.Min.Y > horizontal {
// top
if left {
return 0 // left
} else if right {
return 1 // right
}
} else if rect.Max.Y < horizontal {
// bottom
if left {
return 2 // left
} else if right {
return 3 // right
}
}
return -1
}
// Adds the shape to quad tree and asians it to correct quadrant.
// Proper way is adding all shapes first and then detecting collisions.
// For struct to implement Collidable interface it has to have
// GetRect() *pixel.Rect defined. GetRect function also slightly affects performance.
func (q *Quadtree) Insert(collidable Collidable) {
rect := collidable.GetRect()
// this is little memory expensive but it makes acsesing shapes faster
q.shapes = append(q.shapes, collidable)
if len(q.nodes) != 0 {
i := q.getSub(rect)
if i != -1 {
q.nodes[i].Insert(collidable)
}
return
} else if q.Cap == len(q.shapes) && q.level != q.Depth {
q.split()
for _, s := range q.shapes {
i := q.getSub(s.GetRect())
if i != -1 {
q.nodes[i].Insert(s)
}
}
}
}
// gets smallest generated quadrant that rect fits into
func (q *Quadtree) getQuad(rect *Rect) *Quadtree {
if len(q.nodes) == 0 {
return q
}
subIdx := q.getSub(rect)
if subIdx == -1 {
return q
}
return q.nodes[subIdx].getQuad(rect)
}
// returns all collidables that this rect can possibly collide with
// thought it also returns the shape it self if it wos inserted
func (q *Quadtree) Retrieve(rect *Rect) []Collidable {
return q.getQuad(rect).shapes
}
// returns all coliding shapes
func (q *Quadtree) GetColliding(collidable Collidable) []Collidable {
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
// other wise you will run out of memory eventually and tree will not even work properly
func (q *Quadtree) Clear() {
q.shapes = []Collidable{}
q.nodes = []*Quadtree{}
}