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