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  }