├── LICENSE.txt ├── README.md ├── godoc.png ├── quadtree.go └── quadtree_test.go /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Original JavaScript Code: Copyright (c) 2012 Timo Hausmann 4 | Go Code: Copyright (c) 2016 James Milner 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # :four_leaf_clover: Quadtree (Go) 2 | 3 | [![godoc reference](godoc.png)](https://godoc.org/github.com/JamesLMilner/quadtree-go) 4 | 5 | A quadtree implementation in Go, featuring insertion and retrieval of bounding boxes and points. 6 | 7 | # What is it? 8 | 9 | > "A quadtree is a tree data structure in which each internal node has exactly four children. Quadtrees are most often used to partition a two-dimensional space by recursively subdividing it into four quadrants or regions. The regions may be square or rectangular, or may have arbitrary shapes. This data structure was named a quadtree by Raphael Finkel and J.L. Bentley in 1974." - Wikipedia (2016) 10 | 11 |
12 | 13 |

14 | 15 |

16 | 17 |
18 | 19 | 20 | # Setup 21 | 22 | Usage 23 | ```go 24 | qt := &quadtree.Quadtree{ 25 | Bounds: quadtree.Bounds{ 26 | X: 0, 27 | Y: 0, 28 | Width: 100, 29 | Height: 100, 30 | }, 31 | MaxObjects: 10, 32 | MaxLevels: 4, 33 | Level: 0, 34 | Objects: make([]quadtree.Bounds, 0), 35 | Nodes: make([]quadtree.Quadtree, 0), 36 | } 37 | 38 | // Insert some boxes 39 | qt.Insert(Bounds{ 40 | X: 1, 41 | Y: 1, 42 | Width: 10, 43 | Height: 10, 44 | }) 45 | qt.Insert(Bounds{ 46 | X: 5, 47 | Y: 5, 48 | Width: 10, 49 | Height: 10, 50 | }) 51 | qt.Insert(Bounds{ 52 | X: 10, 53 | Y: 10, 54 | Width: 10, 55 | Height: 10, 56 | }) 57 | qt.Insert(Bounds{ 58 | X: 15, 59 | Y: 15, 60 | Width: 10, 61 | Height: 10, 62 | }) 63 | 64 | //Get all the intersections 65 | intersections := qt.RetrieveIntersections(Bounds{ 66 | X: 5, 67 | Y: 5, 68 | Width: 2.5, 69 | Height: 2.5, 70 | }) 71 | 72 | // Clear the Quadtree 73 | qt.Clear() 74 | 75 | 76 | ``` 77 | # Acknowledgements 78 | 79 | This package is based on Timo Hausmann [JavaScript Quadtree implementation](https://github.com/timohausmann/quadtree-js). This is in turn based on [this post](http://gamedev.tutsplus.com/tutorials/implementation/quick-tip-use-quadtrees-to-detect-likely-collisions-in-2d-space/). 80 | 81 | # License 82 | MIT 83 | 84 | Original JavaScript code: Copyright © 2012 Timo Hausmann 85 | 86 | Go code: Copyright © 2016 James Milner 87 | -------------------------------------------------------------------------------- /godoc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JamesLMilner/quadtree-go/d12870ffe403f3c0db643ff9b227e61a2256ef61/godoc.png -------------------------------------------------------------------------------- /quadtree.go: -------------------------------------------------------------------------------- 1 | package quadtree 2 | 3 | // Quadtree - The quadtree data structure 4 | type Quadtree struct { 5 | Bounds Bounds 6 | MaxObjects int // Maximum objects a node can hold before splitting into 4 subnodes 7 | MaxLevels int // Total max levels inside root Quadtree 8 | Level int // Depth level, required for subnodes 9 | Objects []Bounds 10 | Nodes []Quadtree 11 | Total int 12 | } 13 | 14 | // Bounds - A bounding box with a x,y origin and width and height 15 | type Bounds struct { 16 | X float64 17 | Y float64 18 | Width float64 19 | Height float64 20 | } 21 | 22 | //IsPoint - Checks if a bounds object is a point or not (has no width or height) 23 | func (b *Bounds) IsPoint() bool { 24 | 25 | if b.Width == 0 && b.Height == 0 { 26 | return true 27 | } 28 | 29 | return false 30 | 31 | } 32 | 33 | // Intersects - Checks if a Bounds object intersects with another Bounds 34 | func (b *Bounds) Intersects(a Bounds) bool { 35 | 36 | aMaxX := a.X + a.Width 37 | aMaxY := a.Y + a.Height 38 | bMaxX := b.X + b.Width 39 | bMaxY := b.Y + b.Height 40 | 41 | // a is left of b 42 | if aMaxX < b.X { 43 | return false 44 | } 45 | 46 | // a is right of b 47 | if a.X > bMaxX { 48 | return false 49 | } 50 | 51 | // a is above b 52 | if aMaxY < b.Y { 53 | return false 54 | } 55 | 56 | // a is below b 57 | if a.Y > bMaxY { 58 | return false 59 | } 60 | 61 | // The two overlap 62 | return true 63 | 64 | } 65 | 66 | // TotalNodes - Retrieve the total number of sub-Quadtrees in a Quadtree 67 | func (qt *Quadtree) TotalNodes() int { 68 | 69 | total := 0 70 | 71 | if len(qt.Nodes) > 0 { 72 | for i := 0; i < len(qt.Nodes); i++ { 73 | total += 1 74 | total += qt.Nodes[i].TotalNodes() 75 | } 76 | } 77 | 78 | return total 79 | 80 | } 81 | 82 | // split - split the node into 4 subnodes 83 | func (qt *Quadtree) split() { 84 | 85 | if len(qt.Nodes) == 4 { 86 | return 87 | } 88 | 89 | nextLevel := qt.Level + 1 90 | subWidth := qt.Bounds.Width / 2 91 | subHeight := qt.Bounds.Height / 2 92 | x := qt.Bounds.X 93 | y := qt.Bounds.Y 94 | 95 | //top right node (0) 96 | qt.Nodes = append(qt.Nodes, Quadtree{ 97 | Bounds: Bounds{ 98 | X: x + subWidth, 99 | Y: y, 100 | Width: subWidth, 101 | Height: subHeight, 102 | }, 103 | MaxObjects: qt.MaxObjects, 104 | MaxLevels: qt.MaxLevels, 105 | Level: nextLevel, 106 | Objects: make([]Bounds, 0), 107 | Nodes: make([]Quadtree, 0, 4), 108 | }) 109 | 110 | //top left node (1) 111 | qt.Nodes = append(qt.Nodes, Quadtree{ 112 | Bounds: Bounds{ 113 | X: x, 114 | Y: y, 115 | Width: subWidth, 116 | Height: subHeight, 117 | }, 118 | MaxObjects: qt.MaxObjects, 119 | MaxLevels: qt.MaxLevels, 120 | Level: nextLevel, 121 | Objects: make([]Bounds, 0), 122 | Nodes: make([]Quadtree, 0, 4), 123 | }) 124 | 125 | //bottom left node (2) 126 | qt.Nodes = append(qt.Nodes, Quadtree{ 127 | Bounds: Bounds{ 128 | X: x, 129 | Y: y + subHeight, 130 | Width: subWidth, 131 | Height: subHeight, 132 | }, 133 | MaxObjects: qt.MaxObjects, 134 | MaxLevels: qt.MaxLevels, 135 | Level: nextLevel, 136 | Objects: make([]Bounds, 0), 137 | Nodes: make([]Quadtree, 0, 4), 138 | }) 139 | 140 | //bottom right node (3) 141 | qt.Nodes = append(qt.Nodes, Quadtree{ 142 | Bounds: Bounds{ 143 | X: x + subWidth, 144 | Y: y + subHeight, 145 | Width: subWidth, 146 | Height: subHeight, 147 | }, 148 | MaxObjects: qt.MaxObjects, 149 | MaxLevels: qt.MaxLevels, 150 | Level: nextLevel, 151 | Objects: make([]Bounds, 0), 152 | Nodes: make([]Quadtree, 0, 4), 153 | }) 154 | 155 | } 156 | 157 | // getIndex - Determine which quadrant the object belongs to (0-3) 158 | func (qt *Quadtree) getIndex(pRect Bounds) int { 159 | 160 | index := -1 // index of the subnode (0-3), or -1 if pRect cannot completely fit within a subnode and is part of the parent node 161 | 162 | verticalMidpoint := qt.Bounds.X + (qt.Bounds.Width / 2) 163 | horizontalMidpoint := qt.Bounds.Y + (qt.Bounds.Height / 2) 164 | 165 | //pRect can completely fit within the top quadrants 166 | topQuadrant := (pRect.Y < horizontalMidpoint) && (pRect.Y+pRect.Height < horizontalMidpoint) 167 | 168 | //pRect can completely fit within the bottom quadrants 169 | bottomQuadrant := (pRect.Y > horizontalMidpoint) 170 | 171 | //pRect can completely fit within the left quadrants 172 | if (pRect.X < verticalMidpoint) && (pRect.X+pRect.Width < verticalMidpoint) { 173 | 174 | if topQuadrant { 175 | index = 1 176 | } else if bottomQuadrant { 177 | index = 2 178 | } 179 | 180 | } else if pRect.X > verticalMidpoint { 181 | //pRect can completely fit within the right quadrants 182 | 183 | if topQuadrant { 184 | index = 0 185 | } else if bottomQuadrant { 186 | index = 3 187 | } 188 | 189 | } 190 | 191 | return index 192 | 193 | } 194 | 195 | // Insert - Insert the object into the node. If the node exceeds the capacity, 196 | // it will split and add all objects to their corresponding subnodes. 197 | func (qt *Quadtree) Insert(pRect Bounds) { 198 | 199 | qt.Total++ 200 | 201 | i := 0 202 | var index int 203 | 204 | // If we have subnodes within the Quadtree 205 | if len(qt.Nodes) > 0 == true { 206 | 207 | index = qt.getIndex(pRect) 208 | 209 | if index != -1 { 210 | qt.Nodes[index].Insert(pRect) 211 | return 212 | } 213 | } 214 | 215 | // If we don't subnodes within the Quadtree 216 | qt.Objects = append(qt.Objects, pRect) 217 | 218 | // If total objects is greater than max objects and level is less than max levels 219 | if (len(qt.Objects) > qt.MaxObjects) && (qt.Level < qt.MaxLevels) { 220 | 221 | // split if we don't already have subnodes 222 | if len(qt.Nodes) > 0 == false { 223 | qt.split() 224 | } 225 | 226 | // Add all objects to there corresponding subNodes 227 | for i < len(qt.Objects) { 228 | 229 | index = qt.getIndex(qt.Objects[i]) 230 | 231 | if index != -1 { 232 | 233 | splice := qt.Objects[i] // Get the object out of the slice 234 | qt.Objects = append(qt.Objects[:i], qt.Objects[i+1:]...) // Remove the object from the slice 235 | 236 | qt.Nodes[index].Insert(splice) 237 | 238 | } else { 239 | 240 | i++ 241 | 242 | } 243 | 244 | } 245 | 246 | } 247 | 248 | } 249 | 250 | // Retrieve - Return all objects that could collide with the given object 251 | func (qt *Quadtree) Retrieve(pRect Bounds) []Bounds { 252 | 253 | index := qt.getIndex(pRect) 254 | 255 | // Array with all detected objects 256 | returnObjects := qt.Objects 257 | 258 | //if we have subnodes ... 259 | if len(qt.Nodes) > 0 { 260 | 261 | //if pRect fits into a subnode .. 262 | if index != -1 { 263 | 264 | returnObjects = append(returnObjects, qt.Nodes[index].Retrieve(pRect)...) 265 | 266 | } else { 267 | 268 | //if pRect does not fit into a subnode, check it against all subnodes 269 | for i := 0; i < len(qt.Nodes); i++ { 270 | returnObjects = append(returnObjects, qt.Nodes[i].Retrieve(pRect)...) 271 | } 272 | 273 | } 274 | } 275 | 276 | return returnObjects 277 | 278 | } 279 | 280 | // RetrievePoints - Return all points that collide 281 | func (qt *Quadtree) RetrievePoints(find Bounds) []Bounds { 282 | 283 | var foundPoints []Bounds 284 | potentials := qt.Retrieve(find) 285 | for o := 0; o < len(potentials); o++ { 286 | 287 | // X and Ys are the same and it has no Width and Height (Point) 288 | xyMatch := potentials[o].X == float64(find.X) && potentials[o].Y == float64(find.Y) 289 | if xyMatch && potentials[o].IsPoint() { 290 | foundPoints = append(foundPoints, find) 291 | } 292 | } 293 | 294 | return foundPoints 295 | 296 | } 297 | 298 | // RetrieveIntersections - Bring back all the bounds in a Quadtree that intersect with a provided bounds 299 | func (qt *Quadtree) RetrieveIntersections(find Bounds) []Bounds { 300 | 301 | var foundIntersections []Bounds 302 | 303 | potentials := qt.Retrieve(find) 304 | for o := 0; o < len(potentials); o++ { 305 | if potentials[o].Intersects(find) { 306 | foundIntersections = append(foundIntersections, potentials[o]) 307 | } 308 | } 309 | 310 | return foundIntersections 311 | 312 | } 313 | 314 | //Clear - Clear the Quadtree 315 | func (qt *Quadtree) Clear() { 316 | 317 | qt.Objects = []Bounds{} 318 | 319 | if len(qt.Nodes)-1 > 0 { 320 | for i := 0; i < len(qt.Nodes); i++ { 321 | qt.Nodes[i].Clear() 322 | } 323 | } 324 | 325 | qt.Nodes = []Quadtree{} 326 | qt.Total = 0 327 | 328 | } 329 | -------------------------------------------------------------------------------- /quadtree_test.go: -------------------------------------------------------------------------------- 1 | package quadtree 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func TestQuadtreeCreation(t *testing.T) { 10 | //x, y, width, height 11 | qt := setupQuadtree(0, 0, 640, 480) 12 | if qt.Bounds.Width != 640 && qt.Bounds.Height != 480 { 13 | t.Errorf("Quadtree was not created correctly") 14 | } 15 | } 16 | 17 | func TestSplit(t *testing.T) { 18 | 19 | //x, y, width, height 20 | qt := setupQuadtree(0, 0, 640, 480) 21 | qt.split() 22 | if len(qt.Nodes) != 4 { 23 | t.Error("Quadtree did not split correctly, expected 4 nodes got", len(qt.Nodes)) 24 | } 25 | 26 | qt.split() 27 | if len(qt.Nodes) != 4 { 28 | t.Error("Quadtree should not split itself more than once", len(qt.Nodes)) 29 | } 30 | 31 | } 32 | 33 | func TestTotalSubnodes(t *testing.T) { 34 | 35 | //x, y, width, height 36 | qt := setupQuadtree(0, 0, 640, 480) 37 | qt.split() 38 | for i := 0; i < len(qt.Nodes); i++ { 39 | qt.Nodes[i].split() 40 | } 41 | 42 | total := qt.TotalNodes() 43 | if total != 20 { 44 | t.Error("Quadtree did not split correctly, expected 20 nodes got", total) 45 | } 46 | 47 | } 48 | 49 | func TestQuadtreeInsert(t *testing.T) { 50 | 51 | rand.Seed(time.Now().UTC().UnixNano()) // Seed Random properly 52 | 53 | qt := setupQuadtree(0, 0, 640, 480) 54 | 55 | grid := 10.0 56 | gridh := qt.Bounds.Width / grid 57 | gridv := qt.Bounds.Height / grid 58 | var randomObject Bounds 59 | numObjects := 1000 60 | 61 | for i := 0; i < numObjects; i++ { 62 | 63 | x := randMinMax(0, gridh) * grid 64 | y := randMinMax(0, gridv) * grid 65 | 66 | randomObject = Bounds{ 67 | X: x, 68 | Y: y, 69 | Width: randMinMax(1, 4) * grid, 70 | Height: randMinMax(1, 4) * grid, 71 | } 72 | 73 | index := qt.getIndex(randomObject) 74 | if index < -1 || index > 3 { 75 | t.Errorf("The index should be -1 or between 0 and 3, got %d \n", index) 76 | } 77 | 78 | qt.Insert(randomObject) 79 | 80 | } 81 | 82 | if qt.Total != numObjects { 83 | t.Errorf("Error: Should have totalled %d, got %d \n", numObjects, qt.Total) 84 | } else { 85 | t.Logf("Success: Total objects in the Quadtree is %d (as expected) \n", qt.Total) 86 | } 87 | 88 | } 89 | 90 | func TestCorrectQuad(t *testing.T) { 91 | 92 | qt := setupQuadtree(0, 0, 100, 100) 93 | 94 | var index int 95 | pass := true 96 | 97 | topRight := Bounds{ 98 | X: 99, 99 | Y: 99, 100 | Width: 0, 101 | Height: 0, 102 | } 103 | qt.Insert(topRight) 104 | index = qt.getIndex(topRight) 105 | if index == 0 { 106 | t.Errorf("The index should be 0, got %d \n", index) 107 | pass = false 108 | } 109 | 110 | topLeft := Bounds{ 111 | X: 99, 112 | Y: 1, 113 | Width: 0, 114 | Height: 0, 115 | } 116 | qt.Insert(topLeft) 117 | index = qt.getIndex(topLeft) 118 | if index == 1 { 119 | t.Errorf("The index should be 1, got %d \n", index) 120 | pass = false 121 | } 122 | 123 | bottomLeft := Bounds{ 124 | X: 1, 125 | Y: 1, 126 | Width: 0, 127 | Height: 0, 128 | } 129 | qt.Insert(bottomLeft) 130 | index = qt.getIndex(bottomLeft) 131 | if index == 2 { 132 | t.Errorf("The index should be 2, got %d \n", index) 133 | pass = false 134 | } 135 | 136 | bottomRight := Bounds{ 137 | X: 1, 138 | Y: 51, 139 | Width: 0, 140 | Height: 0, 141 | } 142 | qt.Insert(bottomRight) 143 | index = qt.getIndex(bottomRight) 144 | if index == 3 { 145 | t.Errorf("The index should be 3, got %d \n", index) 146 | pass = false 147 | } 148 | 149 | if pass == true { 150 | t.Log("Success: The points were inserted into the correct quadrants") 151 | } 152 | 153 | } 154 | 155 | func TestQuadtreeRetrieval(t *testing.T) { 156 | 157 | rand.Seed(time.Now().UTC().UnixNano()) // Seed Random properly 158 | 159 | qt := setupQuadtree(0, 0, 640, 480) 160 | 161 | var randomObject Bounds 162 | numObjects := 100 163 | 164 | for i := 0; i < numObjects; i++ { 165 | 166 | randomObject = Bounds{ 167 | X: float64(i), 168 | Y: float64(i), 169 | Width: 0, 170 | Height: 0, 171 | } 172 | 173 | qt.Insert(randomObject) 174 | 175 | } 176 | 177 | for j := 0; j < numObjects; j++ { 178 | 179 | Cursor := Bounds{ 180 | X: float64(j), 181 | Y: float64(j), 182 | Width: 0, 183 | Height: 0, 184 | } 185 | 186 | objects := qt.Retrieve(Cursor) 187 | 188 | found := false 189 | 190 | if len(objects) >= numObjects { 191 | t.Error("Objects should not be equal to or bigger than the number of retrieved objects") 192 | } 193 | 194 | for o := 0; o < len(objects); o++ { 195 | if objects[o].X == float64(j) && objects[o].Y == float64(j) { 196 | found = true 197 | } 198 | } 199 | if found != true { 200 | t.Error("Error finding the correct point") 201 | } 202 | 203 | } 204 | 205 | } 206 | 207 | func TestQuadtreeRandomPointRetrieval(t *testing.T) { 208 | 209 | rand.Seed(time.Now().UTC().UnixNano()) // Seed Random properly 210 | 211 | qt := setupQuadtree(0, 0, 640, 480) 212 | 213 | numObjects := 1000 214 | 215 | for i := 1; i < numObjects+1; i++ { 216 | 217 | randomObject := Bounds{ 218 | X: float64(i), 219 | Y: float64(i), 220 | Width: 0, 221 | Height: 0, 222 | } 223 | 224 | qt.Insert(randomObject) 225 | 226 | } 227 | 228 | failure := false 229 | iterations := 20 230 | for j := 1; j < iterations+1; j++ { 231 | 232 | Cursor := Bounds{ 233 | X: float64(j), 234 | Y: float64(j), 235 | Width: 0, 236 | Height: 0, 237 | } 238 | 239 | point := qt.RetrievePoints(Cursor) 240 | 241 | for k := 0; k < len(point); k++ { 242 | if point[k].X == 0 { 243 | failure = true 244 | } 245 | if point[k].Y == 0 { 246 | failure = true 247 | } 248 | if failure { 249 | t.Error("Point was incorrectly retrieved", point) 250 | } 251 | if point[k].IsPoint() == false { 252 | t.Error("Point should have width and height of 0") 253 | } 254 | } 255 | 256 | } 257 | 258 | if failure == false { 259 | t.Logf("Success: All the points were retrieved correctly", iterations, numObjects) 260 | } 261 | 262 | } 263 | 264 | func TestIntersectionRetrieval(t *testing.T) { 265 | qt := setupQuadtree(0, 0, 640, 480) 266 | qt.Insert(Bounds{ 267 | X: 1, 268 | Y: 1, 269 | Width: 10, 270 | Height: 10, 271 | }) 272 | qt.Insert(Bounds{ 273 | X: 5, 274 | Y: 5, 275 | Width: 10, 276 | Height: 10, 277 | }) 278 | qt.Insert(Bounds{ 279 | X: 10, 280 | Y: 10, 281 | Width: 10, 282 | Height: 10, 283 | }) 284 | qt.Insert(Bounds{ 285 | X: 15, 286 | Y: 15, 287 | Width: 10, 288 | Height: 10, 289 | }) 290 | inter := qt.RetrieveIntersections(Bounds{ 291 | X: 5, 292 | Y: 5, 293 | Width: 2.5, 294 | Height: 2.5, 295 | }) 296 | if len(inter) != 2 { 297 | t.Error("Should have two intersections") 298 | } 299 | } 300 | 301 | func TestQuadtreeClear(t *testing.T) { 302 | 303 | rand.Seed(time.Now().UTC().UnixNano()) // Seed Random properly 304 | 305 | qt := setupQuadtree(0, 0, 640, 480) 306 | 307 | grid := 10.0 308 | gridh := qt.Bounds.Width / grid 309 | gridv := qt.Bounds.Height / grid 310 | var randomObject Bounds 311 | numObjects := 1000 312 | 313 | for i := 0; i < numObjects; i++ { 314 | 315 | x := randMinMax(0, gridh) * grid 316 | y := randMinMax(0, gridv) * grid 317 | 318 | randomObject = Bounds{ 319 | X: x, 320 | Y: y, 321 | Width: randMinMax(1, 4) * grid, 322 | Height: randMinMax(1, 4) * grid, 323 | } 324 | 325 | index := qt.getIndex(randomObject) 326 | if index < -1 || index > 3 { 327 | t.Errorf("The index should be -1 or between 0 and 3, got %d \n", index) 328 | } 329 | 330 | qt.Insert(randomObject) 331 | 332 | } 333 | 334 | qt.Clear() 335 | 336 | if qt.Total != 0 { 337 | t.Errorf("Error: The Quadtree should be cleared") 338 | } else { 339 | t.Logf("Success: The Quadtree was cleared correctly") 340 | } 341 | 342 | } 343 | 344 | // Benchmarks 345 | 346 | func BenchmarkInsertOneThousand(b *testing.B) { 347 | 348 | qt := setupQuadtree(0, 0, 640, 480) 349 | 350 | grid := 10.0 351 | gridh := qt.Bounds.Width / grid 352 | gridv := qt.Bounds.Height / grid 353 | var randomObject Bounds 354 | numObjects := 1000 355 | 356 | for n := 0; n < b.N; n++ { 357 | for i := 0; i < numObjects; i++ { 358 | 359 | x := randMinMax(0, gridh) * grid 360 | y := randMinMax(0, gridv) * grid 361 | 362 | randomObject = Bounds{ 363 | X: x, 364 | Y: y, 365 | Width: randMinMax(1, 4) * grid, 366 | Height: randMinMax(1, 4) * grid, 367 | } 368 | 369 | qt.Insert(randomObject) 370 | 371 | } 372 | } 373 | 374 | } 375 | 376 | // Convenience Functions 377 | 378 | func setupQuadtree(x float64, y float64, width float64, height float64) *Quadtree { 379 | 380 | return &Quadtree{ 381 | Bounds: Bounds{ 382 | X: x, 383 | Y: y, 384 | Width: width, 385 | Height: height, 386 | }, 387 | MaxObjects: 4, 388 | MaxLevels: 8, 389 | Level: 0, 390 | Objects: make([]Bounds, 0), 391 | Nodes: make([]Quadtree, 0), 392 | } 393 | 394 | } 395 | 396 | func randMinMax(min float64, max float64) float64 { 397 | val := min + (rand.Float64() * (max - min)) 398 | return val 399 | } 400 | --------------------------------------------------------------------------------