2020-02-26 08:40:32 -05:00
|
|
|
package d2map
|
|
|
|
|
|
|
|
import (
|
|
|
|
"math"
|
|
|
|
"sort"
|
2020-04-11 14:56:47 -04:00
|
|
|
|
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
2020-02-26 08:40:32 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
type MapEntitiesSearcher interface {
|
|
|
|
// Returns all map entities.
|
|
|
|
All() []MapEntity
|
|
|
|
// Add adds an entity to the index and re-sorts.
|
|
|
|
Add(entities ...MapEntity)
|
|
|
|
// Remove finds and removes the entity from the index.
|
|
|
|
Remove(entity MapEntity)
|
|
|
|
// SearchByRect get entities in a rectangle, results will be sorted top left to bottom right.
|
|
|
|
// Elements with equal Y will be sorted by X
|
|
|
|
SearchByRect(rect d2common.Rectangle) []MapEntity
|
|
|
|
// SearchByRadius get entities in a circle, results will be sorted top left to bottom right.
|
|
|
|
// Elements with equal Y will be sorted by X
|
|
|
|
SearchByRadius(originX, originY, radius float64) []MapEntity
|
|
|
|
// Update re-sorts the index, must be ran after each update.
|
|
|
|
Update()
|
|
|
|
}
|
|
|
|
|
|
|
|
// rangeSearcher a basic index of entity locations using a slice ordered by Y then X coordinates.
|
|
|
|
// Eventually this should be probably replaced with a proper spatial index.
|
|
|
|
type rangeSearcher struct {
|
|
|
|
entities []MapEntity
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewRangeSearcher() MapEntitiesSearcher {
|
|
|
|
return &rangeSearcher{
|
|
|
|
entities: make([]MapEntity, 0, 64),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *rangeSearcher) All() []MapEntity {
|
|
|
|
return r.entities
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *rangeSearcher) Add(entities ...MapEntity) {
|
|
|
|
r.entities = append(r.entities, entities...)
|
|
|
|
|
|
|
|
r.Update()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *rangeSearcher) Remove(entity MapEntity) {
|
|
|
|
if entity == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// In-place filter to remove the given entity.
|
|
|
|
n := 0
|
|
|
|
for _, check := range r.entities {
|
|
|
|
if check != entity {
|
|
|
|
r.entities[n] = check
|
|
|
|
n++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
r.entities = r.entities[:n]
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *rangeSearcher) SearchByRect(rect d2common.Rectangle) []MapEntity {
|
|
|
|
left, top, right, bottom := float64(rect.Left), float64(rect.Top), float64(rect.Right()), float64(rect.Bottom())
|
|
|
|
topIndex := sort.Search(len(r.entities), func(i int) bool {
|
|
|
|
x, y := r.entities[i].GetPosition()
|
|
|
|
if y == top {
|
|
|
|
return x >= left
|
|
|
|
}
|
|
|
|
return y >= top
|
|
|
|
})
|
|
|
|
|
|
|
|
matches := make([]MapEntity, 0, 16)
|
|
|
|
|
|
|
|
for i := topIndex; i < len(r.entities); i++ {
|
|
|
|
x, y := r.entities[i].GetPosition()
|
|
|
|
if y > bottom {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
if x >= left && x <= right {
|
|
|
|
matches = append(matches, r.entities[i])
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return matches
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *rangeSearcher) SearchByRadius(originX, originY, radius float64) []MapEntity {
|
|
|
|
left, right := originX-radius, originX+radius
|
|
|
|
top, bottom := originY-radius, originY+radius
|
|
|
|
inRect := r.SearchByRect(d2common.Rectangle{
|
|
|
|
Left: int(left),
|
|
|
|
Top: int(top),
|
|
|
|
Width: int(right - left),
|
|
|
|
Height: int(bottom - top),
|
|
|
|
})
|
|
|
|
|
|
|
|
// In-place filter to remove entities outside the radius.
|
|
|
|
n := 0
|
|
|
|
for _, check := range inRect {
|
|
|
|
x, y := check.GetPosition()
|
|
|
|
if distance(originX, originY, x, y) <= radius {
|
|
|
|
inRect[n] = check
|
|
|
|
n++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return inRect[:n]
|
|
|
|
}
|
|
|
|
|
|
|
|
func distance(x1, y1, x2, y2 float64) float64 {
|
|
|
|
return math.Abs(math.Sqrt(math.Pow(x2-x1, 2) + math.Pow(y2-y1, 2)))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Re-sorts the index after entities have moved.
|
|
|
|
// Uses bubble sort to target O(n) sort time, in most cases no entities will be swapped.
|
|
|
|
func (r *rangeSearcher) Update() {
|
|
|
|
bubbleSort(r.entities, func(i, j int) bool {
|
|
|
|
ix, iy := r.entities[i].GetPosition()
|
|
|
|
jx, jy := r.entities[j].GetPosition()
|
|
|
|
if iy == jy {
|
|
|
|
return ix < jx
|
|
|
|
}
|
|
|
|
return iy < jy
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func bubbleSort(items []MapEntity, less func(i, j int) bool) {
|
|
|
|
var (
|
|
|
|
n = len(items)
|
|
|
|
sorted = false
|
|
|
|
)
|
|
|
|
for !sorted {
|
|
|
|
swapped := false
|
|
|
|
for i := 0; i < n-1; i++ {
|
|
|
|
if less(i+1, i) {
|
|
|
|
items[i+1], items[i] = items[i], items[i+1]
|
|
|
|
swapped = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !swapped {
|
|
|
|
sorted = true
|
|
|
|
}
|
|
|
|
n = n - 1
|
|
|
|
}
|
|
|
|
}
|