├── readme.md ├── .gitignore ├── quickselect.go ├── node.go ├── rbush.go └── rbush_test.go /readme.md: -------------------------------------------------------------------------------- 1 | go-rbush 2 | ======== 3 | 4 | To get the package, execute: 5 | 6 | go get gopkg.in/nazariglez/go-rbush.v0 7 | 8 | To import this package, add the following line to your code: 9 | 10 | import "gopkg.in/nazariglez/go-rbush.v0" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### JetBrains template 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 5 | 6 | # User-specific stuff: 7 | .idea/workspace.xml 8 | .idea/tasks.xml 9 | .idea/dictionaries 10 | .idea/vcs.xml 11 | .idea/jsLibraryMappings.xml 12 | 13 | # Sensitive or high-churn files: 14 | .idea/dataSources.ids 15 | .idea/dataSources.xml 16 | .idea/dataSources.local.xml 17 | .idea/sqlDataSources.xml 18 | .idea/dynamic.xml 19 | .idea/uiDesigner.xml 20 | 21 | # Gradle: 22 | .idea/gradle.xml 23 | .idea/libraries 24 | 25 | # Mongo Explorer plugin: 26 | .idea/mongoSettings.xml 27 | 28 | ## File-based project format: 29 | *.iws 30 | 31 | ## Plugin-specific files: 32 | 33 | # IntelliJ 34 | /out/ 35 | 36 | # mpeltonen/sbt-idea plugin 37 | .idea_modules/ 38 | 39 | # JIRA plugin 40 | atlassian-ide-plugin.xml 41 | 42 | # Crashlytics plugin (for Android Studio and IntelliJ) 43 | com_crashlytics_export_strings.xml 44 | crashlytics.properties 45 | crashlytics-build.properties 46 | fabric.properties 47 | 48 | -------------------------------------------------------------------------------- /quickselect.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by nazarigonzalez on 21/9/16. 3 | */ 4 | 5 | package rbush 6 | 7 | import ( 8 | "math" 9 | ) 10 | 11 | type Box struct { 12 | MinX, MaxX, MinY, MaxY float64 13 | Data interface{} 14 | } 15 | 16 | type callback func(a, b *Box) float64 17 | 18 | func QuickSelect(arr []Box, k int, left int, right int, compare callback) { 19 | kf := float64(k) 20 | lf := float64(left) 21 | rf := float64(right) 22 | quickSelect(arr, k, kf, left, lf, right, rf, compare) 23 | } 24 | 25 | func QuickSelectDefault(arr []Box, k int, compare callback) { 26 | kf := float64(k) 27 | right := len(arr) - 1 28 | rf := float64(right) 29 | quickSelect(arr, k, kf, 0, 0.0, right, rf, compare) 30 | } 31 | 32 | func quickSelect(arr []Box, k int, kf float64, left int, lf float64, right int, rf float64, compare callback) { 33 | 34 | for right > left { 35 | if right-left > 600 { 36 | var ss float64 37 | 38 | n := rf - lf + 1 39 | m := kf - lf + 1 40 | z := math.Log(n) 41 | s := 0.5 * math.Exp(2*z/3) 42 | 43 | if m-n/2 < 0 { 44 | ss = -1 45 | } else { 46 | ss = 1 47 | } 48 | 49 | sd := 0.5 * math.Sqrt(z*s*(n-s)*ss) 50 | nl := math.Floor(kf - m*s/n + sd) 51 | nr := math.Floor(kf + (n-m)*s/n + sd) 52 | newLeft := math.Max(lf, nl) 53 | newRight := math.Min(rf, nr) 54 | 55 | quickSelect(arr, k, kf, int(newLeft), newLeft, int(newRight), newRight, compare) 56 | } 57 | 58 | t := arr[k] 59 | i := left 60 | j := right 61 | 62 | swap(arr, left, k) 63 | if compare(&arr[right], &t) > 0 { 64 | swap(arr, left, right) 65 | } 66 | 67 | for i < j { 68 | swap(arr, i, j) 69 | i++ 70 | j-- 71 | 72 | for compare(&arr[i], &t) < 0 { 73 | i++ 74 | } 75 | 76 | for compare(&arr[j], &t) > 0 { 77 | j-- 78 | } 79 | } 80 | 81 | if compare(&arr[left], &t) == 0 { 82 | swap(arr, left, j) 83 | } else { 84 | j++ 85 | swap(arr, j, right) 86 | } 87 | 88 | if j <= k { 89 | left = j + 1 90 | } 91 | 92 | if k <= j { 93 | right = j - 1 94 | } 95 | 96 | } 97 | } 98 | 99 | func swap(arr []Box, i, j int) { 100 | tmp := arr[i] 101 | arr[i] = arr[j] 102 | arr[j] = tmp 103 | } 104 | -------------------------------------------------------------------------------- /node.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by nazarigonzalez on 5/10/16. 3 | */ 4 | 5 | package rbush 6 | 7 | import ( 8 | "math" 9 | "sort" 10 | ) 11 | 12 | type node struct { 13 | children []*node 14 | height int 15 | leaf bool 16 | MinX, MaxX, MinY, MaxY float64 17 | box *Box 18 | } 19 | 20 | type byMinX []*node 21 | type byMinY []*node 22 | 23 | func (a byMinX) Len() int { return len(a) } 24 | func (a byMinY) Len() int { return len(a) } 25 | func (a byMinX) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 26 | func (a byMinY) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 27 | func (a byMinX) Less(i, j int) bool { return a[i].MinX < a[j].MinX } 28 | func (a byMinY) Less(i, j int) bool { return a[i].MinY < a[j].MinY } 29 | 30 | func sortByMinX(nodes *[]*node) { 31 | sort.Sort(byMinX(*nodes)) 32 | } 33 | 34 | func sortByMinY(nodes *[]*node) { 35 | sort.Sort(byMinY(*nodes)) 36 | } 37 | 38 | func (node *node) calcBBox() { 39 | node.distBBox(node, 0, len(node.children)) 40 | } 41 | 42 | func (destNode *node) distBBox(node *node, k int, p int) { 43 | destNode.MinX = math.Inf(1) 44 | destNode.MinY = math.Inf(1) 45 | destNode.MaxX = math.Inf(-1) 46 | destNode.MaxY = math.Inf(-1) 47 | 48 | for i := k; i < p; i++ { 49 | destNode.extend(node.children[i]) 50 | } 51 | } 52 | 53 | func (node *node) extend(b *node) { 54 | node.MinX = math.Min(node.MinX, b.MinX) 55 | node.MinY = math.Min(node.MinY, b.MinY) 56 | node.MaxX = math.Max(node.MaxX, b.MaxX) 57 | node.MaxY = math.Max(node.MaxY, b.MaxY) 58 | } 59 | 60 | func compareNodeMinX(a, b *Box) float64 { 61 | return a.MinX - b.MinX 62 | } 63 | 64 | func compareNodeMinY(a, b *Box) float64 { 65 | return a.MinY - b.MinY 66 | } 67 | 68 | func bboxArea(a *node) float64 { 69 | return (a.MaxX - a.MinX) * (a.MaxY - a.MinY) 70 | } 71 | 72 | func bboxMargin(a *node) float64 { 73 | return a.MaxX - a.MinX + (a.MaxY - a.MinY) 74 | } 75 | 76 | func enlargedArea(a, b *node) float64 { 77 | return (math.Max(b.MaxX, a.MaxX) - math.Min(b.MinX, a.MinX)) * (math.Max(b.MaxY, a.MaxY) - math.Min(b.MinY, a.MinY)) 78 | } 79 | 80 | func contains(a *Box, b *node) bool { 81 | return a.MinX <= b.MinX && 82 | a.MinY <= b.MinY && 83 | b.MaxX <= a.MaxX && 84 | b.MaxY <= a.MaxY 85 | } 86 | 87 | func intersects(a *Box, b *node) bool { 88 | return b.MinX <= a.MaxX && 89 | b.MinY <= a.MaxY && 90 | b.MaxX >= a.MinX && 91 | b.MaxY >= a.MinY 92 | } 93 | 94 | func intersectionArea(a, b *node) float64 { 95 | minX := math.Max(a.MinX, b.MinX) 96 | minY := math.Max(a.MinY, b.MinY) 97 | maxX := math.Min(a.MaxX, b.MaxX) 98 | maxY := math.Min(a.MaxY, b.MaxY) 99 | 100 | return math.Max(0, maxX-minX) * math.Max(0, maxY-minY) 101 | } 102 | 103 | func createNode(children []*node) *node { 104 | return &node{ 105 | children: children, 106 | height: 1, 107 | leaf: true, 108 | MinX: math.Inf(1), 109 | MinY: math.Inf(1), 110 | MaxX: math.Inf(-1), 111 | MaxY: math.Inf(-1), 112 | } 113 | } 114 | 115 | func allDistMargin(_node *node, m, M int, prop string) float64 { 116 | if prop == "x" { 117 | sortByMinX(&_node.children) 118 | } else { 119 | sortByMinY(&_node.children) 120 | } 121 | 122 | leftBBox := createNode([]*node{}) 123 | leftBBox.distBBox(_node, 0, m) 124 | rightBBox := createNode([]*node{}) 125 | rightBBox.distBBox(_node, M-m, M) 126 | 127 | margin := bboxMargin(leftBBox) + bboxMargin(rightBBox) 128 | 129 | var child *node 130 | 131 | for i := m; i < M-m; i++ { 132 | child = _node.children[i] 133 | leftBBox.extend(child) 134 | margin += bboxMargin(leftBBox) 135 | } 136 | 137 | for i := M - m - 1; i >= m; i-- { 138 | child = _node.children[i] 139 | rightBBox.extend(child) 140 | margin += bboxMargin(rightBBox) 141 | } 142 | 143 | return margin 144 | } 145 | 146 | func chooseSplitIndex(_node *node, m, M int) int { 147 | var bbox1, bbox2 *node 148 | var overlap, area float64 149 | var i, index int 150 | 151 | minOverlap := math.Inf(1) 152 | minArea := math.Inf(1) 153 | 154 | for i = m; i <= M-m; i++ { 155 | bbox1 = createNode([]*node{}) 156 | bbox1.distBBox(_node, 0, i) 157 | bbox2 = createNode([]*node{}) 158 | bbox2.distBBox(_node, i, M) 159 | 160 | overlap = intersectionArea(bbox1, bbox2) 161 | area = bboxArea(bbox1) + bboxArea(bbox2) 162 | 163 | if overlap < minOverlap { 164 | minOverlap = overlap 165 | index = i 166 | 167 | if area < minArea { 168 | minArea = area 169 | } 170 | } else if overlap == minOverlap { 171 | if area < minArea { 172 | minArea = area 173 | index = i 174 | } 175 | } 176 | } 177 | return index 178 | } 179 | 180 | func (_node *node) copyBox(box *Box) { 181 | _node.MinX = box.MinX 182 | _node.MinY = box.MinY 183 | _node.MaxX = box.MaxX 184 | _node.MaxY = box.MaxY 185 | _node.box = box 186 | } 187 | 188 | func boxToNodes(arr []Box) []*node { 189 | var _n *node 190 | nodes := []*node{} 191 | for i := 0; i < len(arr); i++ { 192 | _n = createNode([]*node{}) 193 | _n.copyBox(&arr[i]) 194 | nodes = append(nodes, _n) 195 | } 196 | return nodes 197 | } 198 | -------------------------------------------------------------------------------- /rbush.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by nazarigonzalez on 21/9/16. 3 | */ 4 | 5 | package rbush 6 | 7 | import ( 8 | "math" 9 | ) 10 | 11 | type RBush struct { 12 | maxEntries float64 13 | minEntries float64 14 | data *node 15 | } 16 | 17 | func NewRBush(maxEntries float64) RBush { 18 | if maxEntries == 0 { 19 | maxEntries = 9 20 | } 21 | 22 | return RBush{ 23 | maxEntries: math.Max(4.0, maxEntries), 24 | minEntries: math.Max(2.0, math.Ceil(maxEntries*0.4)), 25 | data: createNode([]*node{}), 26 | } 27 | } 28 | 29 | func (rbush *RBush) Load(data []Box) { 30 | l := len(data) 31 | if l < int(rbush.minEntries) { 32 | for i := 0; i < l; i++ { 33 | rbush.InsertBox(&data[i]) 34 | } 35 | return 36 | } 37 | 38 | right := l - 1 39 | _node := rbush.build(copySliceBox(data), 0, 0.0, right, float64(right), 0) 40 | if len(rbush.data.children) == 0 { 41 | rbush.data = _node 42 | } else if rbush.data.height == _node.height { 43 | rbush.splitRoot(rbush.data, _node) 44 | } else { 45 | if rbush.data.height < _node.height { 46 | tmpNode := rbush.data 47 | rbush.data = _node 48 | _node = tmpNode 49 | } 50 | 51 | rbush.insert(_node, rbush.data.height-_node.height-1) 52 | } 53 | } 54 | 55 | func (rbush *RBush) build(items []Box, left int, lf float64, right int, rf float64, height int) *node { 56 | var _node *node 57 | 58 | N := rf - lf + 1 59 | M := rbush.maxEntries 60 | 61 | if N <= M { 62 | _node = createNode(boxToNodes(items[left : right+1])) 63 | _node.calcBBox() 64 | return _node 65 | } 66 | 67 | if height == 0 { 68 | heightFloat := math.Ceil(math.Log(N) / math.Log(M)) 69 | height = int(heightFloat) 70 | 71 | M = math.Ceil(N / math.Pow(M, heightFloat-1)) 72 | } 73 | 74 | _node = createNode([]*node{}) 75 | _node.leaf = false 76 | _node.height = height 77 | 78 | N2 := math.Ceil(N / M) 79 | N1 := N2 * math.Ceil(math.Sqrt(M)) 80 | 81 | var right2, right3 float64 82 | 83 | multiselect(items, lf, rf, N1, compareNodeMinX) 84 | 85 | for i := lf; i <= rf; i += N1 { 86 | right2 = math.Min(i+N1-1, rf) 87 | multiselect(items, i, right2, N2, compareNodeMinY) 88 | 89 | for j := i; j <= right2; j += N2 { 90 | right3 = math.Min(j+N2-1, right2) 91 | 92 | _node.children = append(_node.children, rbush.build(items, int(j), j, int(right3), right3, height-1)) 93 | } 94 | } 95 | 96 | _node.calcBBox() 97 | return _node 98 | } 99 | 100 | func (rbush *RBush) splitRoot(_node *node, newNode *node) { 101 | rbush.data = createNode([]*node{_node, newNode}) 102 | rbush.data.height = _node.height + 1 103 | rbush.data.leaf = false 104 | rbush.data.calcBBox() 105 | } 106 | 107 | func (rbush *RBush) InsertBox(box *Box) { 108 | _node := createNode([]*node{}) 109 | _node.copyBox(box) 110 | rbush.insert(_node, rbush.data.height-1) 111 | } 112 | 113 | func (rbush *RBush) insert(item *node, level int) { 114 | insertPath := []*node{} 115 | _node := rbush.chooseSubtree(item, rbush.data, level, &insertPath) 116 | 117 | _node.children = append(_node.children, item) 118 | _node.extend(item) 119 | 120 | maxEntriesInt := int(rbush.maxEntries) 121 | 122 | for level >= 0 { 123 | if len(insertPath[level].children) > maxEntriesInt { 124 | rbush.split(insertPath, &level) 125 | level-- 126 | } else { 127 | break 128 | } 129 | } 130 | 131 | rbush.adjustParentBBoxes(item, &insertPath, &level) 132 | } 133 | 134 | func (rbush *RBush) Search(box *Box) []*Box { 135 | _node := rbush.data 136 | result := []*Box{} 137 | 138 | if !intersects(box, _node) { 139 | return result 140 | } 141 | 142 | nodesToSearch := []*node{} 143 | 144 | for { 145 | ll := len(_node.children) 146 | for i := 0; i < ll; i++ { 147 | child := _node.children[i] 148 | 149 | if intersects(box, child) { 150 | if _node.leaf { 151 | result = append(result, child.box) 152 | } else if contains(box, child) { 153 | rbush.all(child, &result) 154 | } else { 155 | nodesToSearch = append(nodesToSearch, child) 156 | } 157 | } 158 | } 159 | 160 | if len(nodesToSearch) != 0 { 161 | _node = nodesToSearch[len(nodesToSearch)-1] 162 | nodesToSearch[len(nodesToSearch)-1] = nil 163 | nodesToSearch = nodesToSearch[:len(nodesToSearch)-1] 164 | } else { 165 | break 166 | } 167 | } 168 | 169 | return result 170 | } 171 | 172 | func (rbush *RBush) all(item *node, result *[]*Box) *[]*Box { 173 | nodesToSearch := []*node{} 174 | 175 | for { 176 | 177 | if item.leaf { 178 | for i := 0; i < len(item.children); i++ { 179 | *result = append(*result, item.children[i].box) 180 | } 181 | } else { 182 | nodesToSearch = append(nodesToSearch, item.children...) 183 | } 184 | 185 | if len(nodesToSearch) != 0 { 186 | item = nodesToSearch[len(nodesToSearch)-1] 187 | nodesToSearch[len(nodesToSearch)-1] = nil 188 | nodesToSearch = nodesToSearch[:len(nodesToSearch)-1] 189 | } else { 190 | break 191 | } 192 | } 193 | 194 | return result 195 | } 196 | 197 | func (rbush *RBush) adjustParentBBoxes(item *node, path *[]*node, level *int) { 198 | for i := *level; i >= 0; i-- { 199 | (*path)[i].extend(item) 200 | } 201 | } 202 | 203 | func (rbush *RBush) split(path []*node, level *int) { 204 | _node := path[*level] 205 | M := len(_node.children) 206 | m := int(rbush.minEntries) 207 | 208 | rbush.chooseSplitAxis(_node, m, M) 209 | splitIndex := chooseSplitIndex(_node, m, M) 210 | 211 | nodeCopy := make([]*node, len(_node.children)-splitIndex) 212 | copy(nodeCopy, _node.children[splitIndex:]) 213 | newNode := createNode(nodeCopy) 214 | newNode.height = _node.height 215 | newNode.leaf = _node.leaf 216 | 217 | llen := len(_node.children) 218 | for i := splitIndex; i < llen; i++ { 219 | _node.children[i] = nil 220 | } 221 | _node.children = _node.children[:splitIndex] 222 | 223 | _node.calcBBox() 224 | newNode.calcBBox() 225 | 226 | if *level > 0 { 227 | child := &path[*level-1].children 228 | *child = append(*child, newNode) 229 | } else { 230 | rbush.splitRoot(_node, newNode) 231 | } 232 | } 233 | 234 | func (rbush *RBush) chooseSplitAxis(_node *node, m, M int) { 235 | xMargin := allDistMargin(_node, m, M, "x") 236 | yMargin := allDistMargin(_node, m, M, "y") 237 | 238 | if xMargin < yMargin { 239 | sortByMinX(&_node.children) 240 | } 241 | } 242 | 243 | func (rbush *RBush) chooseSubtree(item *node, _node *node, level int, path *[]*node) *node { 244 | var ( 245 | targetNode, child *node 246 | area, enlargement, minArea, minEnlargement float64 247 | ) 248 | 249 | definedNode := false 250 | 251 | for { 252 | *path = append(*path, _node) 253 | 254 | if _node.leaf || len(*path)-1 == level { 255 | break 256 | } 257 | 258 | minArea = math.Inf(1) 259 | minEnlargement = math.Inf(1) 260 | 261 | ll := len(_node.children) 262 | for i := 0; i < ll; i++ { 263 | 264 | child = _node.children[i] 265 | area = bboxArea(child) 266 | enlargement = enlargedArea(item, child) - area 267 | 268 | if enlargement < minEnlargement { 269 | minEnlargement = enlargement 270 | if area < minArea { 271 | minArea = area 272 | } 273 | targetNode = child 274 | definedNode = true 275 | } else if enlargement == minEnlargement { 276 | if area < minArea { 277 | minArea = area 278 | targetNode = child 279 | definedNode = true 280 | } 281 | } 282 | 283 | } 284 | 285 | if definedNode { 286 | _node = targetNode 287 | } else { 288 | _node = _node.children[0] 289 | } 290 | 291 | } 292 | 293 | return _node 294 | } 295 | 296 | func (rbush *RBush) Clear() { 297 | rbush.data = createNode([]*node{}) 298 | } 299 | 300 | func multiselect(arr []Box, left float64, right float64, n float64, compare callback) { 301 | stack := []float64{left, right} 302 | 303 | for len(stack) > 0 { 304 | right, left = stack[len(stack)-1], stack[len(stack)-2] 305 | stack = stack[:len(stack)-2] 306 | 307 | if right-left <= n { 308 | continue 309 | } 310 | 311 | mid := left + math.Ceil((right-left)/n/2)*n 312 | QuickSelect(arr, int(mid), int(left), int(right), compare) 313 | stack = append(stack, left, mid, mid, right) 314 | } 315 | } 316 | 317 | func copySliceBox(arr []Box) []Box { 318 | arr2 := make([]Box, len(arr)) 319 | copy(arr2, arr) 320 | return arr2 321 | } 322 | -------------------------------------------------------------------------------- /rbush_test.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by nazarigonzalez on 6/10/16. 3 | */ 4 | 5 | package rbush 6 | 7 | import ( 8 | "math" 9 | "math/rand" 10 | "testing" 11 | ) 12 | 13 | var R RBush 14 | 15 | func testCompareMinX(a, b *Box) float64 { 16 | return a.MinX - b.MinX 17 | } 18 | 19 | func TestQuickSelectDefault(t *testing.T) { 20 | a := []float64{65, 28, 59, 33, 21, 56, 22, 95, 50, 12, 90, 53, 28, 77, 39} 21 | arr2 := []Box{} 22 | for i := 0; i < len(a); i++ { 23 | arr2 = append(arr2, Box{MinX: a[i]}) 24 | } 25 | 26 | QuickSelectDefault(arr2, 8, testCompareMinX) 27 | 28 | valid := true 29 | res := []float64{39, 28, 28, 33, 21, 12, 22, 50, 53, 56, 59, 65, 90, 77, 95} 30 | for i := 0; i < len(arr2); i++ { 31 | if arr2[i].MinX != res[i] { 32 | valid = false 33 | break 34 | } 35 | } 36 | 37 | if !valid { 38 | t.Error("Invalid int sequence") 39 | } 40 | } 41 | 42 | func TestQuickSelect(t *testing.T) { 43 | a := []float64{65, 28, 59, 33, 21, 56, 22, 95, 50, 12, 90, 53, 28, 77, 39} 44 | arr2 := []Box{} 45 | for i := 0; i < len(a); i++ { 46 | arr2 = append(arr2, Box{MinX: a[i]}) 47 | } 48 | 49 | QuickSelect(arr2, 8, 0, len(arr2)-1, testCompareMinX) 50 | 51 | valid := true 52 | res := []float64{39, 28, 28, 33, 21, 12, 22, 50, 53, 56, 59, 65, 90, 77, 95} 53 | for i := 0; i < len(arr2); i++ { 54 | if arr2[i].MinX != res[i] { 55 | valid = false 56 | break 57 | } 58 | } 59 | 60 | if !valid { 61 | t.Error("Invalid int sequence") 62 | } 63 | } 64 | 65 | func TestNewRBush(t *testing.T) { 66 | R = NewRBush(9) 67 | 68 | if len(R.data.children) != 0 { 69 | t.Error("Expected len children = 0") 70 | } else if !R.data.leaf { 71 | t.Error("Expected leaf = false") 72 | } else if R.data.height != 1 { 73 | t.Error("Expected height = 1") 74 | } 75 | } 76 | 77 | func TestRbush_InsertBox(t *testing.T) { 78 | bbox := Box{ 79 | MinX: 10, 80 | MinY: 20, 81 | MaxX: 30, 82 | MaxY: 40, 83 | } 84 | 85 | R.InsertBox(&bbox) 86 | 87 | if len(R.data.children) != 1 { 88 | t.Error("Expected len children = 1") 89 | } else if R.data.height != 1 { 90 | t.Error("Expected height = 1") 91 | } else if !R.data.leaf { 92 | t.Error("Expected leaf = false") 93 | } else if R.data.MinX != bbox.MinX || R.data.MinY != bbox.MinY || R.data.MaxX != bbox.MaxX || R.data.MaxY != bbox.MaxY { 94 | t.Error("Expected Same bbox in node and box") 95 | } 96 | } 97 | 98 | func TestRbush_Clear(t *testing.T) { 99 | if len(R.data.children) != 1 { 100 | t.Error("Expected len children 1") 101 | } 102 | 103 | R.Clear() 104 | 105 | if len(R.data.children) != 0 { 106 | t.Error("Expected len children 0") 107 | } 108 | } 109 | 110 | func TestRbush_InsertBox2(t *testing.T) { 111 | for i := 0.0; i < 20; i++ { 112 | R.InsertBox(&Box{ 113 | MinX: i*10 - (i + 1), 114 | MinY: i*10 - (i + 1), 115 | MaxX: i*15 - (i + 1), 116 | MaxY: i*15 - (i + 1), 117 | Data: i, 118 | }) 119 | } 120 | 121 | finalValues := Box{ 122 | MinX: -1, 123 | MinY: -1, 124 | MaxX: 265, 125 | MaxY: 265, 126 | } 127 | 128 | if len(R.data.children) != 4 { 129 | t.Error("Expected len children 4") 130 | } else if R.data.height != 2 { 131 | t.Error("Expected height 2") 132 | } else if R.data.leaf { 133 | t.Error("Expected leaf false") 134 | } else if R.data.MinX != finalValues.MinX || R.data.MinY != finalValues.MinY || R.data.MaxX != finalValues.MaxX || R.data.MaxY != finalValues.MaxY { 135 | t.Error("Invalid final Box values") 136 | } 137 | 138 | box1 := Box{MaxX: 41, MaxY: 41, MinX: -1, MinY: -1} 139 | box2 := Box{MaxX: 97, MaxY: 97, MinX: 35, MinY: 35} 140 | box3 := Box{MaxX: 153, MaxY: 153, MinX: 71, MinY: 71} 141 | box4 := Box{MaxX: 265, MaxY: 265, MinX: 107, MinY: 107} 142 | 143 | var bbox *Box 144 | 145 | for i := 0; i < len(R.data.children); i++ { 146 | children := R.data.children[i] 147 | 148 | if i < 3 && len(children.children) != 4 { 149 | t.Error("Expected 4 childrens") 150 | break 151 | } else if i == 3 && len(children.children) != 8 { 152 | t.Error("Expected 8 childrens") 153 | break 154 | } else if children.height != 1 { 155 | t.Error("Expected children height 1") 156 | break 157 | } else if !children.leaf { 158 | t.Error("Expected children leaf true") 159 | break 160 | } 161 | 162 | switch i { 163 | case 0: 164 | bbox = &box1 165 | case 1: 166 | bbox = &box2 167 | case 2: 168 | bbox = &box3 169 | case 3: 170 | bbox = &box4 171 | } 172 | 173 | if children.MinX != bbox.MinX || children.MinY != bbox.MinY || children.MaxX != bbox.MaxX || children.MaxY != bbox.MaxY { 174 | t.Error("Expected valid box values") 175 | break 176 | } 177 | } 178 | } 179 | 180 | func TestRbush_Search(t *testing.T) { 181 | boxes := R.Search(&Box{ 182 | MinX: 20, MinY: 20, 183 | MaxX: 45, MaxY: 45, 184 | }) 185 | 186 | if len(boxes) != 4 { 187 | t.Error("Expected 4 collision boxes") 188 | } 189 | 190 | collisionId := []float64{2, 3, 4, 5} 191 | 192 | for i := range boxes { 193 | id := boxes[i].Data.(float64) 194 | valid := false 195 | 196 | for j := range collisionId { 197 | if collisionId[j] == id { 198 | valid = true 199 | break 200 | } 201 | } 202 | 203 | if !valid { 204 | t.Error("Expected a valid collision box ID") 205 | break 206 | } 207 | } 208 | } 209 | 210 | func TestRbush_Search2(t *testing.T) { 211 | boxes := R.Search(&Box{ 212 | MinX: 20, MinY: 20, 213 | MaxX: 300, MaxY: 300, 214 | }) 215 | 216 | if len(boxes) != 18 { 217 | t.Error("Expected 18 collision boxes") 218 | } 219 | } 220 | 221 | func TestRbush_Load(t *testing.T) { 222 | R.Clear() 223 | 224 | boxes := []Box{} 225 | for i := 0.0; i < 10; i++ { 226 | boxes = append(boxes, Box{ 227 | MinX: i*10 - i*2, 228 | MinY: i*10 - i*2, 229 | MaxX: i*10 - i*2 + i*15 - i*2, 230 | MaxY: i*10 - i*2 + i*15 - i*2, 231 | Data: int(i), 232 | }) 233 | } 234 | 235 | R.Load(boxes) 236 | 237 | if len(R.data.children) != 2 { 238 | t.Error("Expected childre = 2") 239 | } else if len(R.data.children[0].children) != 5 || len(R.data.children[1].children) != 5 { 240 | t.Error("Expected node with 5 childrens") 241 | } 242 | 243 | for i := range R.data.children[0].children { 244 | if R.data.children[0].children[i].box.Data.(int) != i { 245 | t.Error("Expected a box with the same id and index") 246 | break 247 | } 248 | } 249 | 250 | for i := range R.data.children[1].children { 251 | n := i + 5 252 | if R.data.children[1].children[i].box.Data.(int) != n { 253 | t.Error("Expected a box with the same id and index") 254 | break 255 | } 256 | } 257 | } 258 | 259 | func BenchmarkQuickSelect(b *testing.B) { 260 | b.SetBytes(2) 261 | 262 | for i := 0; i < b.N; i++ { 263 | b.StopTimer() 264 | arr := []Box{} 265 | 266 | for i := 0; i < b.N; i++ { 267 | arr = append(arr, Box{ 268 | MinX: rand.Float64(), 269 | MaxX: rand.Float64(), 270 | }) 271 | } 272 | 273 | l := len(arr) 274 | k := int(math.Floor(float64(b.N / 2))) 275 | 276 | b.StartTimer() 277 | QuickSelect(arr, k, 0, l-1, testCompareMinX) 278 | } 279 | } 280 | 281 | func BenchmarkQuickSelectDefault(b *testing.B) { 282 | b.SetBytes(2) 283 | 284 | for i := 0; i < b.N; i++ { 285 | b.StopTimer() 286 | arr := []Box{} 287 | 288 | for i := 0; i < b.N; i++ { 289 | arr = append(arr, Box{ 290 | MinX: rand.Float64(), 291 | MaxX: rand.Float64(), 292 | }) 293 | } 294 | 295 | k := int(math.Floor(float64(b.N / 2))) 296 | b.StartTimer() 297 | QuickSelectDefault(arr, k, testCompareMinX) 298 | } 299 | } 300 | 301 | func BenchmarkRbush_InsertBox(b *testing.B) { 302 | b.StopTimer() 303 | R.Clear() 304 | 305 | boxes := []Box{} 306 | for i := 0; i < b.N; i++ { 307 | boxes = append(boxes, Box{ 308 | MinX: rand.Float64() * 50, 309 | MinY: rand.Float64() * 50, 310 | MaxX: 25 + rand.Float64()*200, 311 | MaxY: 25 + rand.Float64()*200, 312 | }) 313 | } 314 | 315 | for i := 0; i < b.N; i++ { 316 | b.StartTimer() 317 | R.InsertBox(&boxes[i]) 318 | b.StopTimer() 319 | } 320 | } 321 | 322 | func BenchmarkRbush_Load(b *testing.B) { 323 | b.StopTimer() 324 | R.Clear() 325 | 326 | boxes := []Box{} 327 | for i := 0; i < b.N; i++ { 328 | boxes = append(boxes, Box{ 329 | MinX: rand.Float64() * 50, 330 | MinY: rand.Float64() * 50, 331 | MaxX: 25 + rand.Float64()*200, 332 | MaxY: 25 + rand.Float64()*200, 333 | }) 334 | } 335 | 336 | b.StartTimer() 337 | R.Load(boxes) 338 | } 339 | 340 | func BenchmarkRbush_Load2(b *testing.B) { 341 | b.StopTimer() 342 | R.Clear() 343 | 344 | boxes := []Box{} 345 | for i := 0; i < b.N; i++ { 346 | boxes = append(boxes, Box{ 347 | MinX: rand.Float64() * 50, 348 | MinY: rand.Float64() * 50, 349 | MaxX: 25 + rand.Float64()*200, 350 | MaxY: 25 + rand.Float64()*200, 351 | }) 352 | } 353 | 354 | for i := 0; i < b.N; i++ { 355 | R.Clear() 356 | b.StartTimer() 357 | R.Load(boxes) 358 | b.StopTimer() 359 | } 360 | } 361 | 362 | func BenchmarkRbush_Search(b *testing.B) { 363 | b.StopTimer() 364 | 365 | boxes := []Box{} 366 | for i := 0; i < b.N; i++ { 367 | boxes = append(boxes, Box{ 368 | MinX: rand.Float64() * 10, 369 | MinY: rand.Float64() * 10, 370 | MaxX: 10 + rand.Float64()*250, 371 | MaxY: 10 + rand.Float64()*250, 372 | }) 373 | } 374 | 375 | for i := 0; i < b.N; i++ { 376 | b.StartTimer() 377 | _ = R.Search(&boxes[i]) 378 | b.StopTimer() 379 | } 380 | } 381 | --------------------------------------------------------------------------------