go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/sdk/quad/tree.go (about) 1 /* 2 3 Copyright (c) 2023 - Present. Will Charczuk. All rights reserved. 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file at the root of the repository. 5 6 */ 7 8 package quad 9 10 // New returns a new QuadTree with a given set of options. 11 // 12 // Both fields are required, 13 func New[A any](opts ...Option) *Tree[A] { 14 options := Options{ 15 MaxValuesPerQuad: 255, 16 Center: Point{ 17 X: 1024, 18 Y: 1024, 19 }, 20 HalfDimensions: Dimension{ 21 Height: 1024, 22 Width: 1024, 23 }, 24 } 25 for _, opt := range opts { 26 opt(&options) 27 } 28 return &Tree[A]{ 29 maxValuesPerQuad: options.MaxValuesPerQuad, 30 values: make([]PointValue[A], 0, options.MaxValuesPerQuad), 31 preallocateValueStorage: options.PreallocateValueStorage, 32 Range: Range{ 33 Center: options.Center, 34 HalfDimensions: options.HalfDimensions, 35 }, 36 } 37 } 38 39 // Tree creates a quad tree structure to organize 40 // values with given points for fast lookups. 41 type Tree[A any] struct { 42 Range 43 44 root *Tree[A] 45 maxValuesPerQuad int 46 preallocateValueStorage bool 47 48 depth int 49 zone Zone 50 values []PointValue[A] 51 52 parent *Tree[A] 53 nw *Tree[A] 54 ne *Tree[A] 55 sw *Tree[A] 56 se *Tree[A] 57 } 58 59 // Option is a function that mutates QuadTreeOptions 60 type Option func(*Options) 61 62 // OptMaxValuesPerQuad sets the `MaxValuesPerQuad` field on the options. 63 func OptMaxValuesPerQuad(maxValuesPerQuad int) Option { 64 return func(opts *Options) { 65 opts.MaxValuesPerQuad = maxValuesPerQuad 66 } 67 } 68 69 // OptCenter sets the `Center` field on the options. 70 func OptCenter(center Point) Option { 71 return func(opts *Options) { 72 opts.Center = center 73 } 74 } 75 76 // OptHalfDimensions sets the `HalfDimensions` field on the options. 77 func OptHalfDimensions(halfDimensions Dimension) Option { 78 return func(opts *Options) { 79 opts.HalfDimensions = halfDimensions 80 } 81 } 82 83 // OptPreallocateValueStorage sets the `PreallocateValueStorage` field on the options. 84 func OptPreallocateValueStorage(preallocateValueStorage bool) Option { 85 return func(opts *Options) { 86 opts.PreallocateValueStorage = preallocateValueStorage 87 } 88 } 89 90 // Options is options for a quad tree's constructor. 91 type Options struct { 92 MaxValuesPerQuad int 93 Center Point 94 HalfDimensions Dimension 95 PreallocateValueStorage bool 96 } 97 98 // Insert adds a new point with a given value. 99 func (qt *Tree[A]) Insert(p Point, v A) bool { 100 if !qt.ContainsPoint(p) { 101 return false 102 } 103 if len(qt.values) <= qt.maxValuesPerQuad && qt.nw == nil { 104 qt.values = append(qt.values, PointValue[A]{ 105 Point: p, 106 Zone: qt.zone, 107 Value: v, 108 }) 109 return true 110 } 111 if qt.nw == nil { 112 qt.subdivide(qt.preallocateValueStorage) 113 } 114 if qt.nw.Insert(p, v) { 115 return true 116 } else if qt.ne.Insert(p, v) { 117 return true 118 } else if qt.sw.Insert(p, v) { 119 return true 120 } else if qt.se.Insert(p, v) { 121 return true 122 } 123 return false 124 } 125 126 // QueryRange queries the tree for all points within a given 127 // range as denoted by a given QuadRange. 128 func (qt *Tree[A]) QueryZone(zone Zone) []PointValue[A] { 129 if zone == "" { 130 return qt.values 131 } 132 133 decoded := DecodeZoneHigh(zone) 134 switch decoded { 135 case SW: 136 if qt.sw == nil { 137 return nil 138 } 139 return qt.sw.QueryZone(ShiftZone(zone)) 140 case SE: 141 if qt.se == nil { 142 return nil 143 } 144 return qt.se.QueryZone(ShiftZone(zone)) 145 case NW: 146 if qt.nw == nil { 147 return nil 148 } 149 return qt.nw.QueryZone(ShiftZone(zone)) 150 case NE: 151 if qt.ne == nil { 152 return nil 153 } 154 return qt.ne.QueryZone(ShiftZone(zone)) 155 } 156 return nil 157 } 158 159 // QueryRange queries the tree for all points within a given 160 // range as denoted by a given QuadRange. 161 func (qt *Tree[A]) QueryRange(qr Range) []PointValue[A] { 162 if !qt.Intersects(qr) { 163 return nil 164 } 165 var output []PointValue[A] 166 for _, p := range qt.values { 167 if qr.ContainsPoint(p.Point) { 168 output = append(output, p) 169 } 170 } 171 if qt.nw == nil { 172 return output 173 } 174 output = append(output, qt.nw.QueryRange(qr)...) 175 output = append(output, qt.ne.QueryRange(qr)...) 176 output = append(output, qt.sw.QueryRange(qr)...) 177 output = append(output, qt.se.QueryRange(qr)...) 178 return output 179 } 180 181 // QueryPoint gets all the values in the quad tree boundary 182 // as found by a single point within the smallest boundary of the tree. 183 func (qt *Tree[A]) QueryPoint(p Point) []PointValue[A] { 184 if !qt.ContainsPoint(p) { 185 return nil 186 } 187 if qt.sw == nil { 188 return qt.values 189 } 190 if qt.sw.ContainsPoint(p) { 191 return qt.sw.QueryPoint(p) 192 } 193 if qt.se.ContainsPoint(p) { 194 return qt.se.QueryPoint(p) 195 } 196 if qt.nw.ContainsPoint(p) { 197 return qt.nw.QueryPoint(p) 198 } 199 if qt.ne.ContainsPoint(p) { 200 return qt.ne.QueryPoint(p) 201 } 202 return nil 203 } 204 205 // ZoneForPoint gets the zone identifier for a given point. 206 func (qt *Tree[A]) ZoneForPoint(p Point) (zone Zone, ok bool) { 207 if !qt.ContainsPoint(p) { 208 return 209 } 210 if qt.sw == nil { 211 zone = qt.zone 212 ok = true 213 return 214 } 215 if qt.sw.ContainsPoint(p) { 216 return qt.sw.ZoneForPoint(p) 217 } 218 if qt.se.ContainsPoint(p) { 219 return qt.se.ZoneForPoint(p) 220 } 221 if qt.nw.ContainsPoint(p) { 222 return qt.nw.ZoneForPoint(p) 223 } 224 if qt.ne.ContainsPoint(p) { 225 return qt.ne.ZoneForPoint(p) 226 } 227 return 228 } 229 230 // Values yields the values in the current tree node. 231 func (qt *Tree[A]) Values() (output []PointValue[A]) { 232 output = make([]PointValue[A], len(qt.values)) 233 copy(output, qt.values) 234 return 235 } 236 237 // ValuesAll yields all the values in the tree. 238 func (qt *Tree[A]) ValuesAll() (output []PointValue[A]) { 239 qt.VisitDepth(func(n *Tree[A]) { 240 output = append(output, n.values...) 241 }) 242 return 243 } 244 245 // VisitDepth visits all the nodes of a quad tree 246 // in depth first order according to the z-traversal rules. 247 // 248 // E.g we visit SW, SE, NW, NE for each node. 249 func (qt *Tree[A]) VisitDepth(fn func(*Tree[A])) { 250 fn(qt) 251 if qt.se != nil { 252 qt.sw.VisitDepth(fn) 253 qt.se.VisitDepth(fn) 254 qt.nw.VisitDepth(fn) 255 qt.ne.VisitDepth(fn) 256 } 257 } 258 259 // subdivide creates 4 separate sub-trees and moves 260 // values into those trees according to their points 261 // and the sub-trees new bounds. 262 func (qt *Tree[A]) subdivide(preallocate bool) { 263 newDimensions := Dimension{ 264 Width: qt.HalfDimensions.Width / 2, 265 Height: qt.HalfDimensions.Height / 2, 266 } 267 var root *Tree[A] 268 if qt.root != nil { 269 root = qt.root 270 } else { 271 root = qt 272 } 273 274 var swValues []PointValue[A] 275 if preallocate { 276 swValues = make([]PointValue[A], 0, qt.maxValuesPerQuad) 277 } 278 qt.sw = &Tree[A]{ 279 root: root, 280 parent: qt, 281 maxValuesPerQuad: qt.maxValuesPerQuad, 282 Range: Range{ 283 Center: Point{ 284 X: qt.Center.X - newDimensions.Width, 285 Y: qt.Center.Y + newDimensions.Height, 286 }, 287 HalfDimensions: newDimensions, 288 }, 289 values: swValues, 290 depth: qt.depth + 1, 291 zone: AppendZone(qt.zone, SW), 292 } 293 294 var seValues []PointValue[A] 295 if preallocate { 296 seValues = make([]PointValue[A], 0, qt.maxValuesPerQuad) 297 } 298 qt.se = &Tree[A]{ 299 root: root, 300 parent: qt, 301 maxValuesPerQuad: qt.maxValuesPerQuad, 302 Range: Range{ 303 Center: Point{ 304 X: qt.Center.X + newDimensions.Width, 305 Y: qt.Center.Y + newDimensions.Height, 306 }, 307 HalfDimensions: newDimensions, 308 }, 309 values: seValues, 310 depth: qt.depth + 1, 311 zone: AppendZone(qt.zone, SE), 312 } 313 314 var nwValues []PointValue[A] 315 if preallocate { 316 nwValues = make([]PointValue[A], 0, qt.maxValuesPerQuad) 317 } 318 qt.nw = &Tree[A]{ 319 root: root, 320 parent: qt, 321 maxValuesPerQuad: qt.maxValuesPerQuad, 322 Range: Range{ 323 Center: Point{ 324 X: qt.Center.X - newDimensions.Width, 325 Y: qt.Center.Y - newDimensions.Height, 326 }, 327 HalfDimensions: newDimensions, 328 }, 329 values: nwValues, 330 depth: qt.depth + 1, 331 zone: AppendZone(qt.zone, NW), 332 } 333 334 var neValues []PointValue[A] 335 if preallocate { 336 neValues = make([]PointValue[A], 0, qt.maxValuesPerQuad) 337 } 338 qt.ne = &Tree[A]{ 339 root: root, 340 parent: qt, 341 maxValuesPerQuad: qt.maxValuesPerQuad, 342 Range: Range{ 343 Center: Point{ 344 X: qt.Center.X + newDimensions.Width, 345 Y: qt.Center.Y - newDimensions.Height, 346 }, 347 HalfDimensions: newDimensions, 348 }, 349 values: neValues, 350 depth: qt.depth + 1, 351 zone: AppendZone(qt.zone, NE), 352 } 353 354 // redistribute the values 355 for _, pv := range qt.values { 356 if qt.sw.Insert(pv.Point, pv.Value) { 357 continue 358 } else if qt.se.Insert(pv.Point, pv.Value) { 359 continue 360 } else if qt.nw.Insert(pv.Point, pv.Value) { 361 continue 362 } else if qt.ne.Insert(pv.Point, pv.Value) { 363 continue 364 } 365 } 366 qt.values = nil 367 }