com.wallstop-studios.unity-helpers 2.0.0-rc76.1 → 2.0.0-rc76.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,6 @@
3
3
  using System;
4
4
  using System.Collections.Generic;
5
5
  using System.Collections.Immutable;
6
- using System.Linq;
7
6
  using Extension;
8
7
  using UnityEngine;
9
8
  using Utils;
@@ -11,27 +10,48 @@
11
10
  [Serializable]
12
11
  public sealed class KDTree<T> : ISpatialTree<T>
13
12
  {
14
- public delegate float Axis<in V>(V element);
13
+ [Serializable]
14
+ public readonly struct Entry
15
+ {
16
+ public readonly T value;
17
+ public readonly Vector2 position;
18
+
19
+ public Entry(T value, Vector2 position)
20
+ {
21
+ this.value = value;
22
+ this.position = position;
23
+ }
24
+ }
15
25
 
16
26
  [Serializable]
17
- public sealed class KDTreeNode<V>
27
+ public sealed class KDTreeNode
18
28
  {
19
29
  public readonly Bounds boundary;
20
- public readonly KDTreeNode<V> left;
21
- public readonly KDTreeNode<V> right;
22
- public readonly V[] elements;
30
+ public readonly KDTreeNode left;
31
+ public readonly KDTreeNode right;
32
+ public readonly Entry[] entries;
23
33
  public readonly bool isTerminal;
24
34
 
25
- public KDTreeNode(
26
- List<V> elements,
27
- Func<V, Vector2> elementTransformer,
28
- int bucketSize,
29
- bool isXAxis,
30
- bool balanced
31
- )
35
+ public KDTreeNode(List<Entry> elements, int bucketSize, bool isXAxis, bool balanced)
32
36
  {
33
- boundary = elements.Select(elementTransformer).GetBounds() ?? new Bounds();
34
- this.elements = elements.ToArray();
37
+ bool initializedBoundary = false;
38
+ Bounds bounds = new();
39
+ foreach (Entry element in elements)
40
+ {
41
+ if (initializedBoundary)
42
+ {
43
+ bounds.Encapsulate(element.position);
44
+ }
45
+ else
46
+ {
47
+ bounds = new Bounds(element.position, new Vector3(0f, 0f, 1f));
48
+ }
49
+
50
+ initializedBoundary = true;
51
+ }
52
+
53
+ boundary = bounds;
54
+ this.entries = 0 < elements.Count ? elements.ToArray() : Array.Empty<Entry>();
35
55
  isTerminal = elements.Count <= bucketSize;
36
56
  if (isTerminal)
37
57
  {
@@ -40,76 +60,79 @@
40
60
 
41
61
  if (balanced)
42
62
  {
43
- Axis<V> axisFunction = isXAxis
44
- ? element => elementTransformer(element).x
45
- : element => elementTransformer(element).y;
46
-
47
- int Comparison(V lhs, V rhs)
63
+ if (isXAxis)
64
+ {
65
+ Array.Sort(
66
+ this.entries,
67
+ (lhs, rhs) => lhs.position.x.CompareTo(rhs.position.x)
68
+ );
69
+ }
70
+ else
48
71
  {
49
- return axisFunction(lhs).CompareTo(axisFunction(rhs));
72
+ Array.Sort(
73
+ this.entries,
74
+ (lhs, rhs) => lhs.position.y.CompareTo(rhs.position.y)
75
+ );
50
76
  }
51
- elements.Sort(Comparison);
52
77
 
53
78
  int cutoff = elements.Count / 2;
54
- left = new KDTreeNode<V>(
55
- elements.Take(cutoff).ToList(),
56
- elementTransformer,
57
- bucketSize,
58
- !isXAxis,
59
- true
60
- );
61
- right = new KDTreeNode<V>(
62
- elements.Skip(cutoff).ToList(),
63
- elementTransformer,
64
- bucketSize,
65
- !isXAxis,
66
- true
67
- );
79
+
80
+ List<Entry> leftList = new();
81
+ List<Entry> rightList = new();
82
+ for (int i = 0; i < this.entries.Length; ++i)
83
+ {
84
+ Entry element = this.entries[i];
85
+ if (i < cutoff)
86
+ {
87
+ leftList.Add(element);
88
+ }
89
+ else
90
+ {
91
+ rightList.Add(element);
92
+ }
93
+ }
94
+
95
+ left = new KDTreeNode(leftList, bucketSize, !isXAxis, true);
96
+ right = new KDTreeNode(rightList, bucketSize, !isXAxis, true);
68
97
  }
69
98
  else
70
99
  {
71
100
  Vector2 cutoff = boundary.center;
72
101
  if (isXAxis)
73
102
  {
74
- left = new KDTreeNode<V>(
75
- elements
76
- .Where(element => elementTransformer(element).x <= cutoff.x)
77
- .ToList(),
78
- elementTransformer,
79
- bucketSize,
80
- false,
81
- false
82
- );
83
- right = new KDTreeNode<V>(
84
- elements
85
- .Where(element => cutoff.x < elementTransformer(element).x)
86
- .ToList(),
87
- elementTransformer,
88
- bucketSize,
89
- false,
90
- false
91
- );
103
+ List<Entry> leftList = new();
104
+ List<Entry> rightList = new();
105
+ foreach (Entry element in this.entries)
106
+ {
107
+ if (element.position.x <= cutoff.x)
108
+ {
109
+ leftList.Add(element);
110
+ }
111
+ else
112
+ {
113
+ rightList.Add(element);
114
+ }
115
+ }
116
+ left = new KDTreeNode(leftList, bucketSize, false, false);
117
+ right = new KDTreeNode(rightList, bucketSize, false, false);
92
118
  }
93
119
  else
94
120
  {
95
- left = new KDTreeNode<V>(
96
- elements
97
- .Where(element => elementTransformer(element).y <= cutoff.y)
98
- .ToList(),
99
- elementTransformer,
100
- bucketSize,
101
- true,
102
- false
103
- );
104
- right = new KDTreeNode<V>(
105
- elements
106
- .Where(element => cutoff.y < elementTransformer(element).y)
107
- .ToList(),
108
- elementTransformer,
109
- bucketSize,
110
- true,
111
- false
112
- );
121
+ List<Entry> leftList = new();
122
+ List<Entry> rightList = new();
123
+ foreach (Entry element in this.entries)
124
+ {
125
+ if (element.position.y <= cutoff.y)
126
+ {
127
+ leftList.Add(element);
128
+ }
129
+ else
130
+ {
131
+ rightList.Add(element);
132
+ }
133
+ }
134
+ left = new KDTreeNode(leftList, bucketSize, true, false);
135
+ right = new KDTreeNode(rightList, bucketSize, true, false);
113
136
  }
114
137
  }
115
138
  }
@@ -119,11 +142,9 @@
119
142
 
120
143
  public readonly ImmutableArray<T> elements;
121
144
  public Bounds Boundary => _bounds;
122
- public Func<T, Vector2> ElementTransformer => _elementTransformer;
123
145
 
124
146
  private readonly Bounds _bounds;
125
- private readonly Func<T, Vector2> _elementTransformer;
126
- private readonly KDTreeNode<T> _head;
147
+ private readonly KDTreeNode _head;
127
148
 
128
149
  public KDTree(
129
150
  IEnumerable<T> points,
@@ -132,74 +153,178 @@
132
153
  bool balanced = true
133
154
  )
134
155
  {
135
- _elementTransformer =
136
- elementTransformer ?? throw new ArgumentNullException(nameof(elementTransformer));
156
+ if (elementTransformer is null)
157
+ {
158
+ throw new ArgumentNullException(nameof(elementTransformer));
159
+ }
137
160
  elements =
138
161
  points?.ToImmutableArray() ?? throw new ArgumentNullException(nameof(points));
139
- _bounds = elements.Select(elementTransformer).GetBounds() ?? new Bounds();
140
- _head = new KDTreeNode<T>(
141
- elements.ToList(),
142
- elementTransformer,
162
+ Bounds bounds = new();
163
+ bool boundsInitialized = false;
164
+ List<Entry> entries = new();
165
+ foreach (T element in elements)
166
+ {
167
+ Vector2 elementPosition = elementTransformer(element);
168
+ if (boundsInitialized)
169
+ {
170
+ bounds.Encapsulate(elementPosition);
171
+ }
172
+ else
173
+ {
174
+ bounds = new Bounds(elementPosition, new Vector3(0f, 0f, 1f));
175
+ }
176
+ boundsInitialized = true;
177
+ entries.Add(new Entry(element, elementPosition));
178
+ }
179
+ _bounds = bounds;
180
+ _head = new KDTreeNode(
181
+ entries,
143
182
  bucketSize: bucketSize,
144
183
  isXAxis: true,
145
184
  balanced: balanced
146
185
  );
147
186
  }
148
187
 
149
- public IEnumerable<T> GetElementsInBounds(Bounds bounds)
188
+ public List<T> GetElementsInRange(
189
+ Vector2 position,
190
+ float range,
191
+ List<T> elementsInRange,
192
+ float minimumRange = 0
193
+ )
150
194
  {
151
- Stack<KDTreeNode<T>> buffer = Buffers<KDTreeNode<T>>.Stack;
152
- return GetElementsInBounds(bounds, buffer);
153
- }
195
+ elementsInRange.Clear();
196
+ Bounds bounds = new(position, new Vector3(range * 2, range * 2, 1f));
154
197
 
155
- public IEnumerable<T> GetElementsInBounds(Bounds bounds, Stack<KDTreeNode<T>> nodeBuffer)
156
- {
157
198
  if (!bounds.FastIntersects2D(_bounds))
158
199
  {
159
- yield break;
200
+ return elementsInRange;
160
201
  }
161
202
 
162
- Stack<KDTreeNode<T>> nodesToVisit = nodeBuffer ?? new Stack<KDTreeNode<T>>();
203
+ Stack<KDTreeNode> nodesToVisit = Buffers<KDTreeNode>.Stack;
163
204
  nodesToVisit.Clear();
164
205
  nodesToVisit.Push(_head);
165
206
 
166
- while (nodesToVisit.TryPop(out KDTreeNode<T> currentNode))
207
+ List<KDTreeNode> resultBuffer = Buffers<KDTreeNode>.List;
208
+ resultBuffer.Clear();
209
+
210
+ while (nodesToVisit.TryPop(out KDTreeNode currentNode))
167
211
  {
168
- if (currentNode.isTerminal)
212
+ if (currentNode.isTerminal || bounds.Overlaps2D(currentNode.boundary))
169
213
  {
170
- foreach (T element in currentNode.elements)
214
+ resultBuffer.Add(currentNode);
215
+ continue;
216
+ }
217
+
218
+ KDTreeNode leftNode = currentNode.left;
219
+ if (0 < leftNode.entries.Length && bounds.FastIntersects2D(leftNode.boundary))
220
+ {
221
+ nodesToVisit.Push(leftNode);
222
+ }
223
+
224
+ KDTreeNode rightNode = currentNode.right;
225
+ if (0 < rightNode.entries.Length && bounds.FastIntersects2D(rightNode.boundary))
226
+ {
227
+ nodesToVisit.Push(rightNode);
228
+ }
229
+ }
230
+
231
+ if (0 < minimumRange)
232
+ {
233
+ float minimumRangeSquared = minimumRange * minimumRange;
234
+ float rangeSquared = range * range;
235
+ foreach (KDTreeNode node in resultBuffer)
236
+ {
237
+ foreach (Entry element in node.entries)
171
238
  {
172
- if (bounds.FastContains2D(_elementTransformer(element)))
239
+ float squareDistance = (element.position - position).sqrMagnitude;
240
+ if (squareDistance <= minimumRangeSquared || rangeSquared < squareDistance)
173
241
  {
174
- yield return element;
242
+ continue;
175
243
  }
244
+
245
+ elementsInRange.Add(element.value);
246
+ }
247
+ }
248
+ }
249
+ else
250
+ {
251
+ float rangeSquared = range * range;
252
+ foreach (KDTreeNode node in resultBuffer)
253
+ {
254
+ foreach (Entry element in node.entries)
255
+ {
256
+ if ((element.position - position).sqrMagnitude <= rangeSquared)
257
+ {
258
+ elementsInRange.Add(element.value);
259
+ }
260
+ }
261
+ }
262
+ }
263
+
264
+ return elementsInRange;
265
+ }
266
+
267
+ public List<T> GetElementsInBounds(Bounds bounds, List<T> elementsInBounds)
268
+ {
269
+ Stack<KDTreeNode> buffer = Buffers<KDTreeNode>.Stack;
270
+ return GetElementsInBounds(bounds, elementsInBounds, buffer);
271
+ }
272
+
273
+ public List<T> GetElementsInBounds(
274
+ Bounds bounds,
275
+ List<T> elementsInBounds,
276
+ Stack<KDTreeNode> nodeBuffer
277
+ )
278
+ {
279
+ elementsInBounds.Clear();
280
+ if (!bounds.FastIntersects2D(_bounds))
281
+ {
282
+ return elementsInBounds;
283
+ }
284
+
285
+ Stack<KDTreeNode> nodesToVisit = nodeBuffer ?? new Stack<KDTreeNode>();
286
+ nodesToVisit.Clear();
287
+ nodesToVisit.Push(_head);
288
+
289
+ while (nodesToVisit.TryPop(out KDTreeNode currentNode))
290
+ {
291
+ if (bounds.Overlaps2D(currentNode.boundary))
292
+ {
293
+ foreach (Entry element in currentNode.entries)
294
+ {
295
+ elementsInBounds.Add(element.value);
176
296
  }
177
297
 
178
298
  continue;
179
299
  }
180
300
 
181
- if (bounds.Overlaps2D(currentNode.boundary))
301
+ if (currentNode.isTerminal)
182
302
  {
183
- foreach (T element in currentNode.elements)
303
+ foreach (Entry element in currentNode.entries)
184
304
  {
185
- yield return element;
305
+ if (bounds.FastContains2D(element.position))
306
+ {
307
+ elementsInBounds.Add(element.value);
308
+ }
186
309
  }
187
310
 
188
311
  continue;
189
312
  }
190
313
 
191
- KDTreeNode<T> leftNode = currentNode.left;
192
- if (0 < leftNode.elements.Length && bounds.FastIntersects2D(leftNode.boundary))
314
+ KDTreeNode leftNode = currentNode.left;
315
+ if (0 < leftNode.entries.Length && bounds.FastIntersects2D(leftNode.boundary))
193
316
  {
194
317
  nodesToVisit.Push(leftNode);
195
318
  }
196
319
 
197
- KDTreeNode<T> rightNode = currentNode.right;
198
- if (0 < rightNode.elements.Length && bounds.FastIntersects2D(rightNode.boundary))
320
+ KDTreeNode rightNode = currentNode.right;
321
+ if (0 < rightNode.entries.Length && bounds.FastIntersects2D(rightNode.boundary))
199
322
  {
200
323
  nodesToVisit.Push(rightNode);
201
324
  }
202
325
  }
326
+
327
+ return elementsInBounds;
203
328
  }
204
329
 
205
330
  public void GetApproximateNearestNeighbors(
@@ -208,14 +333,16 @@
208
333
  List<T> nearestNeighbors
209
334
  )
210
335
  {
211
- Stack<KDTreeNode<T>> nodeBuffer = Buffers<KDTreeNode<T>>.Stack;
336
+ Stack<KDTreeNode> nodeBuffer = Buffers<KDTreeNode>.Stack;
212
337
  HashSet<T> nearestNeighborBuffer = Buffers<T>.HashSet;
338
+ List<Entry> nearestNeighborsCache = Buffers<Entry>.List;
213
339
  GetApproximateNearestNeighbors(
214
340
  position,
215
341
  count,
216
342
  nearestNeighbors,
217
343
  nodeBuffer,
218
- nearestNeighborBuffer
344
+ nearestNeighborBuffer,
345
+ nearestNeighborsCache
219
346
  );
220
347
  }
221
348
 
@@ -224,69 +351,78 @@
224
351
  Vector2 position,
225
352
  int count,
226
353
  List<T> nearestNeighbors,
227
- Stack<KDTreeNode<T>> nodeBuffer,
228
- HashSet<T> nearestNeighborBuffer
354
+ Stack<KDTreeNode> nodeBuffer,
355
+ HashSet<T> nearestNeighborBuffer,
356
+ List<Entry> nearestNeighborsCache
229
357
  )
230
358
  {
231
359
  nearestNeighbors.Clear();
232
360
 
233
- KDTreeNode<T> current = _head;
234
- Stack<KDTreeNode<T>> stack = nodeBuffer ?? new Stack<KDTreeNode<T>>();
235
- stack.Clear();
236
- stack.Push(_head);
237
- HashSet<T> nearestNeighborsSet = nearestNeighborBuffer ?? new HashSet<T>(count);
238
- nearestNeighborsSet.Clear();
361
+ KDTreeNode current = _head;
362
+ nodeBuffer ??= new Stack<KDTreeNode>();
363
+ nodeBuffer.Clear();
364
+ nodeBuffer.Push(_head);
365
+ nearestNeighborBuffer ??= new HashSet<T>(count);
366
+ nearestNeighborBuffer.Clear();
367
+ nearestNeighborsCache ??= new List<Entry>(count);
368
+ nearestNeighborsCache.Clear();
239
369
 
240
370
  while (!current.isTerminal)
241
371
  {
242
- KDTreeNode<T> left = current.left;
243
- KDTreeNode<T> right = current.right;
372
+ KDTreeNode left = current.left;
373
+ KDTreeNode right = current.right;
244
374
  if (
245
375
  ((Vector2)left.boundary.center - position).sqrMagnitude
246
376
  < ((Vector2)right.boundary.center - position).sqrMagnitude
247
377
  )
248
378
  {
249
- stack.Push(left);
379
+ nodeBuffer.Push(left);
250
380
  current = left;
251
- if (left.elements.Length <= count)
381
+ if (left.entries.Length <= count)
252
382
  {
253
383
  break;
254
384
  }
255
385
  }
256
386
  else
257
387
  {
258
- stack.Push(right);
388
+ nodeBuffer.Push(right);
259
389
  current = right;
260
- if (right.elements.Length <= count)
390
+ if (right.entries.Length <= count)
261
391
  {
262
392
  break;
263
393
  }
264
394
  }
265
395
  }
266
396
 
267
- while (nearestNeighborsSet.Count < count && stack.TryPop(out KDTreeNode<T> selected))
397
+ while (
398
+ nearestNeighborBuffer.Count < count && nodeBuffer.TryPop(out KDTreeNode selected)
399
+ )
268
400
  {
269
- foreach (T element in selected.elements)
401
+ foreach (Entry element in selected.entries)
270
402
  {
271
- _ = nearestNeighborsSet.Add(element);
403
+ if (nearestNeighborBuffer.Add(element.value))
404
+ {
405
+ nearestNeighborsCache.Add(element);
406
+ }
272
407
  }
273
408
  }
274
409
 
275
- foreach (T element in nearestNeighborsSet)
276
- {
277
- nearestNeighbors.Add(element);
278
- }
279
- if (count < nearestNeighbors.Count)
410
+ if (count < nearestNeighborsCache.Count)
280
411
  {
281
412
  Vector2 localPosition = position;
282
- nearestNeighbors.Sort(NearestComparison);
283
- nearestNeighbors.RemoveRange(count, nearestNeighbors.Count - count);
413
+ nearestNeighborsCache.Sort(NearestComparison);
284
414
 
285
- int NearestComparison(T lhs, T rhs) =>
286
- (_elementTransformer(lhs) - localPosition).sqrMagnitude.CompareTo(
287
- (_elementTransformer(rhs) - localPosition).sqrMagnitude
415
+ int NearestComparison(Entry lhs, Entry rhs) =>
416
+ (lhs.position - localPosition).sqrMagnitude.CompareTo(
417
+ (rhs.position - localPosition).sqrMagnitude
288
418
  );
289
419
  }
420
+
421
+ nearestNeighbors.Clear();
422
+ for (int i = 0; i < nearestNeighborsCache.Count && i < count; ++i)
423
+ {
424
+ nearestNeighbors.Add(nearestNeighborsCache[i].value);
425
+ }
290
426
  }
291
427
  }
292
428
  }