com.wallstop-studios.unity-helpers 2.0.0-rc14 → 2.0.0-rc16

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.
package/README.md CHANGED
@@ -1,2 +1,2 @@
1
1
  # A Grab-Bag
2
- Various Unity Helpers. Includes deterministic randoms including a PCGRandom implementation,
2
+ Various Unity Helpers. Includes many deterministic, seedable random number generators.
@@ -6,6 +6,7 @@
6
6
  using System.Linq;
7
7
  using Extension;
8
8
  using UnityEngine;
9
+ using Utils;
9
10
 
10
11
  [Serializable]
11
12
  public sealed class KDTree<T> : ISpatialTree<T>
@@ -13,7 +14,7 @@
13
14
  public delegate float Axis<in V>(V element);
14
15
 
15
16
  [Serializable]
16
- private sealed class KDTreeNode<V>
17
+ public sealed class KDTreeNode<V>
17
18
  {
18
19
  public readonly Bounds boundary;
19
20
  public readonly KDTreeNode<V> left;
@@ -21,7 +22,13 @@
21
22
  public readonly V[] elements;
22
23
  public readonly bool isTerminal;
23
24
 
24
- public KDTreeNode(List<V> elements, Func<V, Vector2> elementTransformer, int bucketSize, bool isXAxis, bool balanced)
25
+ public KDTreeNode(
26
+ List<V> elements,
27
+ Func<V, Vector2> elementTransformer,
28
+ int bucketSize,
29
+ bool isXAxis,
30
+ bool balanced
31
+ )
25
32
  {
26
33
  boundary = elements.Select(elementTransformer).GetBounds() ?? new Bounds();
27
34
  this.elements = elements.ToArray();
@@ -44,21 +51,65 @@
44
51
  elements.Sort(Comparison);
45
52
 
46
53
  int cutoff = elements.Count / 2;
47
- left = new KDTreeNode<V>(elements.Take(cutoff).ToList(), elementTransformer, bucketSize, !isXAxis, true);
48
- right = new KDTreeNode<V>(elements.Skip(cutoff).ToList(), elementTransformer, bucketSize, !isXAxis, true);
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
+ );
49
68
  }
50
69
  else
51
70
  {
52
71
  Vector2 cutoff = boundary.center;
53
72
  if (isXAxis)
54
73
  {
55
- left = new KDTreeNode<V>(elements.Where(element => elementTransformer(element).x <= cutoff.x).ToList(), elementTransformer, bucketSize, false, false);
56
- right = new KDTreeNode<V>(elements.Where(element => cutoff.x < elementTransformer(element).x).ToList(), elementTransformer, bucketSize, false, false);
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
+ );
57
92
  }
58
93
  else
59
94
  {
60
- left = new KDTreeNode<V>(elements.Where(element => elementTransformer(element).y <= cutoff.y).ToList(), elementTransformer, bucketSize, true, false);
61
- right = new KDTreeNode<V>(elements.Where(element => cutoff.y < elementTransformer(element).y).ToList(), elementTransformer, bucketSize, true, false);
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
+ );
62
113
  }
63
114
  }
64
115
  }
@@ -74,22 +125,42 @@
74
125
  private readonly Func<T, Vector2> _elementTransformer;
75
126
  private readonly KDTreeNode<T> _head;
76
127
 
77
- public KDTree(IEnumerable<T> points, Func<T, Vector2> elementTransformer, int bucketSize = DefaultBucketSize, bool balanced = true)
128
+ public KDTree(
129
+ IEnumerable<T> points,
130
+ Func<T, Vector2> elementTransformer,
131
+ int bucketSize = DefaultBucketSize,
132
+ bool balanced = true
133
+ )
78
134
  {
79
- _elementTransformer = elementTransformer ?? throw new ArgumentNullException(nameof(elementTransformer));
80
- elements = points?.ToImmutableArray() ?? throw new ArgumentNullException(nameof(points));
135
+ _elementTransformer =
136
+ elementTransformer ?? throw new ArgumentNullException(nameof(elementTransformer));
137
+ elements =
138
+ points?.ToImmutableArray() ?? throw new ArgumentNullException(nameof(points));
81
139
  _bounds = elements.Select(elementTransformer).GetBounds() ?? new Bounds();
82
- _head = new KDTreeNode<T>(elements.ToList(), elementTransformer, bucketSize: bucketSize, isXAxis:true, balanced: balanced);
140
+ _head = new KDTreeNode<T>(
141
+ elements.ToList(),
142
+ elementTransformer,
143
+ bucketSize: bucketSize,
144
+ isXAxis: true,
145
+ balanced: balanced
146
+ );
83
147
  }
84
148
 
85
149
  public IEnumerable<T> GetElementsInBounds(Bounds bounds)
150
+ {
151
+ Stack<KDTreeNode<T>> buffer = Buffers<KDTreeNode<T>>.Stack;
152
+ return GetElementsInBounds(bounds, buffer);
153
+ }
154
+
155
+ public IEnumerable<T> GetElementsInBounds(Bounds bounds, Stack<KDTreeNode<T>> nodeBuffer)
86
156
  {
87
157
  if (!bounds.FastIntersects2D(_bounds))
88
158
  {
89
159
  yield break;
90
160
  }
91
161
 
92
- Stack<KDTreeNode<T>> nodesToVisit = new();
162
+ Stack<KDTreeNode<T>> nodesToVisit = nodeBuffer ?? new Stack<KDTreeNode<T>>();
163
+ nodesToVisit.Clear();
93
164
  nodesToVisit.Push(_head);
94
165
 
95
166
  while (nodesToVisit.TryPop(out KDTreeNode<T> currentNode))
@@ -131,22 +202,49 @@
131
202
  }
132
203
  }
133
204
 
205
+ public void GetApproximateNearestNeighbors(
206
+ Vector2 position,
207
+ int count,
208
+ List<T> nearestNeighbors
209
+ )
210
+ {
211
+ Stack<KDTreeNode<T>> nodeBuffer = Buffers<KDTreeNode<T>>.Stack;
212
+ HashSet<T> nearestNeighborBuffer = Buffers<T>.HashSet;
213
+ GetApproximateNearestNeighbors(
214
+ position,
215
+ count,
216
+ nearestNeighbors,
217
+ nodeBuffer,
218
+ nearestNeighborBuffer
219
+ );
220
+ }
221
+
134
222
  // Heavily adapted http://homepage.divms.uiowa.edu/%7Ekvaradar/sp2012/daa/ann.pdf
135
- public void GetApproximateNearestNeighbors(Vector2 position, int count, List<T> nearestNeighbors)
223
+ public void GetApproximateNearestNeighbors(
224
+ Vector2 position,
225
+ int count,
226
+ List<T> nearestNeighbors,
227
+ Stack<KDTreeNode<T>> nodeBuffer,
228
+ HashSet<T> nearestNeighborBuffer
229
+ )
136
230
  {
137
231
  nearestNeighbors.Clear();
138
232
 
139
233
  KDTreeNode<T> current = _head;
140
- Stack<KDTreeNode<T>> stack = new();
234
+ Stack<KDTreeNode<T>> stack = nodeBuffer ?? new Stack<KDTreeNode<T>>();
235
+ stack.Clear();
141
236
  stack.Push(_head);
142
- HashSet<T> nearestNeighborsSet = new(count);
237
+ HashSet<T> nearestNeighborsSet = nearestNeighborBuffer ?? new HashSet<T>(count);
238
+ nearestNeighborsSet.Clear();
143
239
 
144
240
  while (!current.isTerminal)
145
241
  {
146
242
  KDTreeNode<T> left = current.left;
147
243
  KDTreeNode<T> right = current.right;
148
- if (((Vector2)left.boundary.center - position).sqrMagnitude <
149
- ((Vector2)right.boundary.center - position).sqrMagnitude)
244
+ if (
245
+ ((Vector2)left.boundary.center - position).sqrMagnitude
246
+ < ((Vector2)right.boundary.center - position).sqrMagnitude
247
+ )
150
248
  {
151
249
  stack.Push(left);
152
250
  current = left;
@@ -177,10 +275,16 @@
177
275
  nearestNeighbors.AddRange(nearestNeighborsSet);
178
276
  if (count < nearestNeighbors.Count)
179
277
  {
180
- int NearestComparison(T lhs, T rhs) => (_elementTransformer(lhs) - position).sqrMagnitude.CompareTo((_elementTransformer(rhs) - position).sqrMagnitude);
181
278
  nearestNeighbors.Sort(NearestComparison);
182
279
  nearestNeighbors.RemoveRange(count, nearestNeighbors.Count - count);
183
280
  }
281
+
282
+ return;
283
+
284
+ int NearestComparison(T lhs, T rhs) =>
285
+ (_elementTransformer(lhs) - position).sqrMagnitude.CompareTo(
286
+ (_elementTransformer(rhs) - position).sqrMagnitude
287
+ );
184
288
  }
185
289
  }
186
290
  }
@@ -6,6 +6,7 @@
6
6
  using System.Linq;
7
7
  using Extension;
8
8
  using UnityEngine;
9
+ using Utils;
9
10
 
10
11
  [Serializable]
11
12
  public sealed class QuadTree<T> : ISpatialTree<T>
@@ -13,17 +14,21 @@
13
14
  private const int NumChildren = 4;
14
15
 
15
16
  [Serializable]
16
- private sealed class QuadTreeNode<V>
17
+ public sealed class QuadTreeNode<V>
17
18
  {
18
19
  private static readonly List<V> Buffer = new();
19
20
 
20
21
  public readonly Bounds boundary;
21
- public readonly QuadTreeNode<V>[] children;
22
+ internal readonly QuadTreeNode<V>[] children;
22
23
  public readonly V[] elements;
23
24
  public readonly bool isTerminal;
24
25
 
25
- public QuadTreeNode(V[] elements, Func<V, Vector2> elementTransformer, Bounds boundary,
26
- int bucketSize)
26
+ public QuadTreeNode(
27
+ V[] elements,
28
+ Func<V, Vector2> elementTransformer,
29
+ Bounds boundary,
30
+ int bucketSize
31
+ )
27
32
  {
28
33
  this.boundary = boundary;
29
34
  this.elements = elements;
@@ -34,16 +39,44 @@
34
39
  return;
35
40
  }
36
41
  children = new QuadTreeNode<V>[NumChildren];
37
-
42
+
38
43
  Vector3 quadrantSize = boundary.size / 2f;
39
44
  Vector2 halfQuadrantSize = quadrantSize / 2f;
40
45
 
41
46
  Bounds[] quadrants =
42
47
  {
43
- new Bounds(new Vector3(boundary.center.x - halfQuadrantSize.x, boundary.center.y + halfQuadrantSize.y, boundary.center.z), quadrantSize),
44
- new Bounds(new Vector3(boundary.center.x + halfQuadrantSize.x, boundary.center.y + halfQuadrantSize.y, boundary.center.z), quadrantSize),
45
- new Bounds(new Vector3(boundary.center.x + halfQuadrantSize.x, boundary.center.y - halfQuadrantSize.y, boundary.center.z), quadrantSize),
46
- new Bounds(new Vector3(boundary.center.x - halfQuadrantSize.x, boundary.center.y - halfQuadrantSize.y, boundary.center.z), quadrantSize),
48
+ new Bounds(
49
+ new Vector3(
50
+ boundary.center.x - halfQuadrantSize.x,
51
+ boundary.center.y + halfQuadrantSize.y,
52
+ boundary.center.z
53
+ ),
54
+ quadrantSize
55
+ ),
56
+ new Bounds(
57
+ new Vector3(
58
+ boundary.center.x + halfQuadrantSize.x,
59
+ boundary.center.y + halfQuadrantSize.y,
60
+ boundary.center.z
61
+ ),
62
+ quadrantSize
63
+ ),
64
+ new Bounds(
65
+ new Vector3(
66
+ boundary.center.x + halfQuadrantSize.x,
67
+ boundary.center.y - halfQuadrantSize.y,
68
+ boundary.center.z
69
+ ),
70
+ quadrantSize
71
+ ),
72
+ new Bounds(
73
+ new Vector3(
74
+ boundary.center.x - halfQuadrantSize.x,
75
+ boundary.center.y - halfQuadrantSize.y,
76
+ boundary.center.z
77
+ ),
78
+ quadrantSize
79
+ ),
47
80
  };
48
81
 
49
82
  for (int i = 0; i < quadrants.Length; ++i)
@@ -58,7 +91,12 @@
58
91
  }
59
92
  }
60
93
 
61
- children[i] = new QuadTreeNode<V>(Buffer.ToArray(), elementTransformer, quadrant, bucketSize);
94
+ children[i] = new QuadTreeNode<V>(
95
+ Buffer.ToArray(),
96
+ elementTransformer,
97
+ quadrant,
98
+ bucketSize
99
+ );
62
100
  }
63
101
  }
64
102
  }
@@ -73,23 +111,41 @@
73
111
  private readonly Func<T, Vector2> _elementTransformer;
74
112
  private readonly QuadTreeNode<T> _head;
75
113
 
76
- public QuadTree(IEnumerable<T> points, Func<T, Vector2> elementTransformer, Bounds? boundary = null,
77
- int bucketSize = DefaultBucketSize)
114
+ public QuadTree(
115
+ IEnumerable<T> points,
116
+ Func<T, Vector2> elementTransformer,
117
+ Bounds? boundary = null,
118
+ int bucketSize = DefaultBucketSize
119
+ )
78
120
  {
79
- _elementTransformer = elementTransformer ?? throw new ArgumentNullException(nameof(elementTransformer));
80
- elements = points?.ToImmutableArray() ?? throw new ArgumentNullException(nameof(points));
121
+ _elementTransformer =
122
+ elementTransformer ?? throw new ArgumentNullException(nameof(elementTransformer));
123
+ elements =
124
+ points?.ToImmutableArray() ?? throw new ArgumentNullException(nameof(points));
81
125
  _bounds = boundary ?? elements.Select(elementTransformer).GetBounds() ?? new Bounds();
82
- _head = new QuadTreeNode<T>(elements.ToArray(), elementTransformer, _bounds, bucketSize);
126
+ _head = new QuadTreeNode<T>(
127
+ elements.ToArray(),
128
+ elementTransformer,
129
+ _bounds,
130
+ bucketSize
131
+ );
83
132
  }
84
133
 
85
134
  public IEnumerable<T> GetElementsInBounds(Bounds bounds)
135
+ {
136
+ Stack<QuadTreeNode<T>> nodeBuffer = Buffers<QuadTreeNode<T>>.Stack;
137
+ return GetElementsInBounds(bounds, nodeBuffer);
138
+ }
139
+
140
+ public IEnumerable<T> GetElementsInBounds(Bounds bounds, Stack<QuadTreeNode<T>> nodeBuffer)
86
141
  {
87
142
  if (!bounds.FastIntersects2D(_bounds))
88
143
  {
89
144
  yield break;
90
145
  }
91
146
 
92
- Stack<QuadTreeNode<T>> nodesToVisit = new();
147
+ Stack<QuadTreeNode<T>> nodesToVisit = nodeBuffer ?? new Stack<QuadTreeNode<T>>();
148
+ nodesToVisit.Clear();
93
149
  nodesToVisit.Push(_head);
94
150
 
95
151
  while (nodesToVisit.TryPop(out QuadTreeNode<T> currentNode))
@@ -134,19 +190,47 @@
134
190
  }
135
191
  }
136
192
 
193
+ public void GetApproximateNearestNeighbors(
194
+ Vector2 position,
195
+ int count,
196
+ List<T> nearestNeighbors
197
+ )
198
+ {
199
+ Stack<QuadTreeNode<T>> nodeBuffer = Buffers<QuadTreeNode<T>>.Stack;
200
+ List<QuadTreeNode<T>> childrenBuffer = Buffers<QuadTreeNode<T>>.List;
201
+ HashSet<T> nearestNeighborBuffer = Buffers<T>.HashSet;
202
+ GetApproximateNearestNeighbors(
203
+ position,
204
+ count,
205
+ nearestNeighbors,
206
+ nodeBuffer,
207
+ childrenBuffer,
208
+ nearestNeighborBuffer
209
+ );
210
+ }
211
+
137
212
  // Heavily adapted http://homepage.divms.uiowa.edu/%7Ekvaradar/sp2012/daa/ann.pdf
138
- public void GetApproximateNearestNeighbors(Vector2 position, int count, List<T> nearestNeighbors)
213
+ public void GetApproximateNearestNeighbors(
214
+ Vector2 position,
215
+ int count,
216
+ List<T> nearestNeighbors,
217
+ Stack<QuadTreeNode<T>> nodeBuffer,
218
+ List<QuadTreeNode<T>> childrenBuffer,
219
+ HashSet<T> nearestNeighborBuffer
220
+ )
139
221
  {
140
222
  nearestNeighbors.Clear();
141
223
 
142
224
  QuadTreeNode<T> current = _head;
143
- Stack<QuadTreeNode<T>> stack = new();
225
+ Stack<QuadTreeNode<T>> stack = nodeBuffer ?? new Stack<QuadTreeNode<T>>();
226
+ stack.Clear();
144
227
  stack.Push(_head);
145
- List<QuadTreeNode<T>> childrenCopy = new(NumChildren);
146
- HashSet<T> nearestNeighborsSet = new(count);
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();
147
233
 
148
- int Comparison(QuadTreeNode<T> lhs, QuadTreeNode<T> rhs) => ((Vector2)lhs.boundary.center - position).sqrMagnitude.CompareTo(((Vector2)rhs.boundary.center - position).sqrMagnitude);
149
-
150
234
  while (!current.isTerminal)
151
235
  {
152
236
  childrenCopy.Clear();
@@ -175,10 +259,21 @@
175
259
  nearestNeighbors.AddRange(nearestNeighborsSet);
176
260
  if (count < nearestNeighbors.Count)
177
261
  {
178
- int NearestComparison(T lhs, T rhs) => (_elementTransformer(lhs) - position).sqrMagnitude.CompareTo((_elementTransformer(rhs) - position).sqrMagnitude);
179
262
  nearestNeighbors.Sort(NearestComparison);
180
263
  nearestNeighbors.RemoveRange(count, nearestNeighbors.Count - count);
181
264
  }
265
+
266
+ return;
267
+
268
+ int Comparison(QuadTreeNode<T> lhs, QuadTreeNode<T> rhs) =>
269
+ ((Vector2)lhs.boundary.center - position).sqrMagnitude.CompareTo(
270
+ ((Vector2)rhs.boundary.center - position).sqrMagnitude
271
+ );
272
+
273
+ int NearestComparison(T lhs, T rhs) =>
274
+ (_elementTransformer(lhs) - position).sqrMagnitude.CompareTo(
275
+ (_elementTransformer(rhs) - position).sqrMagnitude
276
+ );
182
277
  }
183
278
  }
184
279
  }
@@ -6,19 +6,25 @@
6
6
  using System.Linq;
7
7
  using Extension;
8
8
  using UnityEngine;
9
+ using Utils;
9
10
 
10
11
  [Serializable]
11
12
  public sealed class RTree<T>
12
13
  {
13
14
  [Serializable]
14
- private sealed class RTreeNode<V>
15
+ public sealed class RTreeNode<V>
15
16
  {
16
17
  public readonly Bounds boundary;
17
- public readonly RTreeNode<V>[] children;
18
+ internal readonly RTreeNode<V>[] children;
18
19
  public readonly V[] elements;
19
20
  public readonly bool isTerminal;
20
21
 
21
- public RTreeNode(List<V> elements, Func<V, Bounds> elementTransformer, int bucketSize, int branchFactor)
22
+ public RTreeNode(
23
+ List<V> elements,
24
+ Func<V, Bounds> elementTransformer,
25
+ int bucketSize,
26
+ int branchFactor
27
+ )
22
28
  {
23
29
  float minX = float.MaxValue;
24
30
  float minY = float.MaxValue;
@@ -35,7 +41,13 @@
35
41
  maxY = Math.Max(maxY, max.y);
36
42
  }
37
43
 
38
- boundary = elements.Count <= 0 ? new Bounds() : new Bounds(new Vector3(minX + (maxX - minX) / 2, minY + (maxY - minY) / 2), new Vector3(maxX - minX, maxY - minY));
44
+ boundary =
45
+ elements.Count <= 0
46
+ ? new Bounds()
47
+ : new Bounds(
48
+ new Vector3(minX + (maxX - minX) / 2, minY + (maxY - minY) / 2),
49
+ new Vector3(maxX - minX, maxY - minY)
50
+ );
39
51
  this.elements = elements.ToArray();
40
52
  isTerminal = elements.Count <= bucketSize;
41
53
  if (isTerminal)
@@ -61,28 +73,44 @@
61
73
  double slicesPerAxis = Math.Sqrt(branchFactor);
62
74
  int rectanglesPerPagePerAxis = (int)(slicesPerAxis * targetSize);
63
75
 
64
- int XAxis(V lhs, V rhs)
65
- {
66
- return elementTransformer(lhs).center.x.CompareTo(elementTransformer(rhs).center.x);
67
- }
68
-
69
- int YAxis(V lhs, V rhs)
70
- {
71
- return elementTransformer(lhs).center.y.CompareTo(elementTransformer(rhs).center.y);
72
- }
73
-
74
76
  elements.Sort(XAxis);
75
- foreach (List<V> xSlice in elements.Partition(rectanglesPerPagePerAxis).Select(enumerable => enumerable as List<V> ?? enumerable.ToList()))
77
+ foreach (
78
+ List<V> xSlice in elements
79
+ .Partition(rectanglesPerPagePerAxis)
80
+ .Select(enumerable => enumerable as List<V> ?? enumerable.ToList())
81
+ )
76
82
  {
77
83
  xSlice.Sort(YAxis);
78
- foreach (List<V> ySlice in xSlice.Partition(intTargetSize).Select(enumerable => enumerable as List<V> ?? enumerable.ToList()))
84
+ foreach (
85
+ List<V> ySlice in xSlice
86
+ .Partition(intTargetSize)
87
+ .Select(enumerable => enumerable as List<V> ?? enumerable.ToList())
88
+ )
79
89
  {
80
- RTreeNode<V> node = new(ySlice, elementTransformer, bucketSize, branchFactor);
90
+ RTreeNode<V> node = new(
91
+ ySlice,
92
+ elementTransformer,
93
+ bucketSize,
94
+ branchFactor
95
+ );
81
96
  tempChildren.Add(node);
82
97
  }
83
98
  }
84
99
 
85
100
  children = tempChildren.ToArray();
101
+ return;
102
+
103
+ int XAxis(V lhs, V rhs)
104
+ {
105
+ return elementTransformer(lhs)
106
+ .center.x.CompareTo(elementTransformer(rhs).center.x);
107
+ }
108
+
109
+ int YAxis(V lhs, V rhs)
110
+ {
111
+ return elementTransformer(lhs)
112
+ .center.y.CompareTo(elementTransformer(rhs).center.y);
113
+ }
86
114
  }
87
115
  }
88
116
 
@@ -97,16 +125,30 @@
97
125
  private readonly RTreeNode<T> _head;
98
126
 
99
127
  public RTree(
100
- IEnumerable<T> points, Func<T, Bounds> elementTransformer, int bucketSize = DefaultBucketSize,
101
- int branchFactor = DefaultBranchFactor)
128
+ IEnumerable<T> points,
129
+ Func<T, Bounds> elementTransformer,
130
+ int bucketSize = DefaultBucketSize,
131
+ int branchFactor = DefaultBranchFactor
132
+ )
102
133
  {
103
- _elementTransformer = elementTransformer ?? throw new ArgumentNullException(nameof(elementTransformer));
104
- elements = points?.ToImmutableArray() ?? throw new ArgumentNullException(nameof(points));
134
+ _elementTransformer =
135
+ elementTransformer ?? throw new ArgumentNullException(nameof(elementTransformer));
136
+ elements =
137
+ points?.ToImmutableArray() ?? throw new ArgumentNullException(nameof(points));
105
138
  _bounds = elements.Select(elementTransformer).GetBounds() ?? new Bounds();
106
- _head = new RTreeNode<T>(elements.ToList(), elementTransformer, bucketSize, branchFactor);
139
+ _head = new RTreeNode<T>(
140
+ elements.ToList(),
141
+ elementTransformer,
142
+ bucketSize,
143
+ branchFactor
144
+ );
107
145
  }
108
146
 
109
- public IEnumerable<T> GetElementsInRange(Vector2 position, float range, float minimumRange = 0f)
147
+ public IEnumerable<T> GetElementsInRange(
148
+ Vector2 position,
149
+ float range,
150
+ float minimumRange = 0f
151
+ )
110
152
  {
111
153
  Circle area = new(position, range);
112
154
  if (0 < minimumRange)
@@ -115,25 +157,10 @@
115
157
  return GetElementsInBounds(
116
158
  new Bounds(
117
159
  new Vector3(position.x, position.y, 0f),
118
- new Vector3(range * 2f, range * 2f, 1f)))
119
- .Where(
120
- element =>
121
- {
122
- Bounds elementBoundary = _elementTransformer(element);
123
- if (!area.Intersects(elementBoundary))
124
- {
125
- return false;
126
- }
127
-
128
- return !minimumArea.Intersects(elementBoundary);
129
- });
130
- }
131
- return GetElementsInBounds(
132
- new Bounds(
133
- new Vector3(position.x, position.y, 0f),
134
- new Vector3(range * 2f, range * 2f, 1f)))
135
- .Where(
136
- element =>
160
+ new Vector3(range * 2f, range * 2f, 1f)
161
+ )
162
+ )
163
+ .Where(element =>
137
164
  {
138
165
  Bounds elementBoundary = _elementTransformer(element);
139
166
  if (!area.Intersects(elementBoundary))
@@ -141,18 +168,42 @@
141
168
  return false;
142
169
  }
143
170
 
144
- return true;
171
+ return !minimumArea.Intersects(elementBoundary);
145
172
  });
173
+ }
174
+ return GetElementsInBounds(
175
+ new Bounds(
176
+ new Vector3(position.x, position.y, 0f),
177
+ new Vector3(range * 2f, range * 2f, 1f)
178
+ )
179
+ )
180
+ .Where(element =>
181
+ {
182
+ Bounds elementBoundary = _elementTransformer(element);
183
+ if (!area.Intersects(elementBoundary))
184
+ {
185
+ return false;
186
+ }
187
+
188
+ return true;
189
+ });
146
190
  }
147
191
 
148
192
  public IEnumerable<T> GetElementsInBounds(Bounds bounds)
193
+ {
194
+ Stack<RTreeNode<T>> nodeBuffer = Buffers<RTreeNode<T>>.Stack;
195
+ return GetElementsInBounds(bounds, nodeBuffer);
196
+ }
197
+
198
+ public IEnumerable<T> GetElementsInBounds(Bounds bounds, Stack<RTreeNode<T>> nodeBuffer)
149
199
  {
150
200
  if (!bounds.FastIntersects2D(_bounds))
151
201
  {
152
202
  yield break;
153
203
  }
154
204
 
155
- Stack<RTreeNode<T>> nodesToVisit = new();
205
+ Stack<RTreeNode<T>> nodesToVisit = nodeBuffer ?? new Stack<RTreeNode<T>>();
206
+ nodeBuffer.Clear();
156
207
  nodesToVisit.Push(_head);
157
208
 
158
209
  while (nodesToVisit.TryPop(out RTreeNode<T> currentNode))
@@ -197,18 +248,45 @@
197
248
  }
198
249
  }
199
250
 
251
+ public void GetApproximateNearestNeighbors(
252
+ Vector2 position,
253
+ int count,
254
+ List<T> nearestNeighbors
255
+ )
256
+ {
257
+ Stack<RTreeNode<T>> nodeBuffer = Buffers<RTreeNode<T>>.Stack;
258
+ List<RTreeNode<T>> childrenBuffer = Buffers<RTreeNode<T>>.List;
259
+ HashSet<T> nearestNeighborBuffer = Buffers<T>.HashSet;
260
+ GetApproximateNearestNeighbors(
261
+ position,
262
+ count,
263
+ nearestNeighbors,
264
+ nodeBuffer,
265
+ childrenBuffer,
266
+ nearestNeighborBuffer
267
+ );
268
+ }
269
+
200
270
  // Heavily adapted http://homepage.divms.uiowa.edu/%7Ekvaradar/sp2012/daa/ann.pdf
201
- public void GetApproximateNearestNeighbors(Vector2 position, int count, List<T> nearestNeighbors)
271
+ public void GetApproximateNearestNeighbors(
272
+ Vector2 position,
273
+ int count,
274
+ List<T> nearestNeighbors,
275
+ Stack<RTreeNode<T>> nodeBuffer,
276
+ List<RTreeNode<T>> childrenBuffer,
277
+ HashSet<T> nearestNeighborsBuffer
278
+ )
202
279
  {
203
280
  nearestNeighbors.Clear();
204
281
 
205
282
  RTreeNode<T> current = _head;
206
- Stack<RTreeNode<T>> stack = new();
283
+ Stack<RTreeNode<T>> stack = nodeBuffer ?? new Stack<RTreeNode<T>>();
284
+ stack.Clear();
207
285
  stack.Push(_head);
208
- List<RTreeNode<T>> childrenCopy = new();
209
- HashSet<T> nearestNeighborsSet = new(count);
210
-
211
- int Comparison(RTreeNode<T> lhs, RTreeNode<T> rhs) => ((Vector2)lhs.boundary.center - position).sqrMagnitude.CompareTo(((Vector2)rhs.boundary.center - position).sqrMagnitude);
286
+ List<RTreeNode<T>> childrenCopy = childrenBuffer ?? new List<RTreeNode<T>>();
287
+ childrenCopy.Clear();
288
+ HashSet<T> nearestNeighborsSet = nearestNeighborsBuffer ?? new HashSet<T>(count);
289
+ nearestNeighborsSet.Clear();
212
290
 
213
291
  while (!current.isTerminal)
214
292
  {
@@ -238,10 +316,21 @@
238
316
  nearestNeighbors.AddRange(nearestNeighborsSet);
239
317
  if (count < nearestNeighbors.Count)
240
318
  {
241
- int NearestComparison(T lhs, T rhs) => ((Vector2)_elementTransformer(lhs).center - position).sqrMagnitude.CompareTo(((Vector2)_elementTransformer(rhs).center - position).sqrMagnitude);
242
319
  nearestNeighbors.Sort(NearestComparison);
243
320
  nearestNeighbors.RemoveRange(count, nearestNeighbors.Count - count);
244
321
  }
322
+
323
+ return;
324
+
325
+ int Comparison(RTreeNode<T> lhs, RTreeNode<T> rhs) =>
326
+ ((Vector2)lhs.boundary.center - position).sqrMagnitude.CompareTo(
327
+ ((Vector2)rhs.boundary.center - position).sqrMagnitude
328
+ );
329
+
330
+ int NearestComparison(T lhs, T rhs) =>
331
+ ((Vector2)_elementTransformer(lhs).center - position).sqrMagnitude.CompareTo(
332
+ ((Vector2)_elementTransformer(rhs).center - position).sqrMagnitude
333
+ );
245
334
  }
246
335
  }
247
336
  }
@@ -21,15 +21,17 @@
21
21
  {
22
22
  public static Color GetAverageColor(
23
23
  this Sprite sprite,
24
- ColorAveragingMethod method = ColorAveragingMethod.LAB
24
+ ColorAveragingMethod method = ColorAveragingMethod.LAB,
25
+ float alphaCutoff = 0.01f
25
26
  )
26
27
  {
27
- return GetAverageColor(Enumerables.Of(sprite), method);
28
+ return GetAverageColor(Enumerables.Of(sprite), method, alphaCutoff);
28
29
  }
29
30
 
30
31
  public static Color GetAverageColor(
31
32
  this IEnumerable<Sprite> sprites,
32
- ColorAveragingMethod method = ColorAveragingMethod.LAB
33
+ ColorAveragingMethod method = ColorAveragingMethod.LAB,
34
+ float alphaCutoff = 0.01f
33
35
  )
34
36
  {
35
37
  return GetAverageColor(
@@ -41,34 +43,36 @@
41
43
  {
42
44
  texture.MakeReadable();
43
45
  Color[] pixels = texture.GetPixels();
44
- return pixels.Where(pixel => pixel.a > 0.01f);
46
+ return pixels;
45
47
  }),
46
- method
48
+ method,
49
+ alphaCutoff
47
50
  );
48
51
  }
49
52
 
50
53
  public static Color GetAverageColor(
51
54
  this IEnumerable<Color> pixels,
52
- ColorAveragingMethod method = ColorAveragingMethod.LAB
55
+ ColorAveragingMethod method = ColorAveragingMethod.LAB,
56
+ float alphaCutoff = 0.01f
53
57
  )
54
58
  {
55
59
  switch (method)
56
60
  {
57
61
  case ColorAveragingMethod.LAB:
58
62
  {
59
- return AverageInLABSpace(pixels);
63
+ return AverageInLABSpace(pixels, alphaCutoff);
60
64
  }
61
65
  case ColorAveragingMethod.HSV:
62
66
  {
63
- return AverageInHSVSpace(pixels);
67
+ return AverageInHSVSpace(pixels, alphaCutoff);
64
68
  }
65
69
  case ColorAveragingMethod.Weighted:
66
70
  {
67
- return WeightedRGBAverage(pixels);
71
+ return WeightedRGBAverage(pixels, alphaCutoff);
68
72
  }
69
73
  case ColorAveragingMethod.Dominant:
70
74
  {
71
- return GetDominantColor(pixels);
75
+ return GetDominantColor(pixels, alphaCutoff);
72
76
  }
73
77
  default:
74
78
  {
@@ -82,9 +86,12 @@
82
86
  }
83
87
 
84
88
  // CIE L*a*b* space averaging - most perceptually accurate
85
- private static Color AverageInLABSpace(IEnumerable<Color> pixels)
89
+ private static Color AverageInLABSpace(IEnumerable<Color> pixels, float alphaCutoff)
86
90
  {
87
- List<LABColor> labValues = pixels.Select(RGBToLAB).ToList();
91
+ List<LABColor> labValues = pixels
92
+ .Where(pixel => pixel.a > alphaCutoff)
93
+ .Select(RGBToLAB)
94
+ .ToList();
88
95
 
89
96
  double avgL = labValues.Average(lab => lab.l);
90
97
  double avgA = labValues.Average(lab => lab.a);
@@ -94,14 +101,14 @@
94
101
  }
95
102
 
96
103
  // HSV space averaging - good for preserving vibrant colors
97
- private static Color AverageInHSVSpace(IEnumerable<Color> pixels)
104
+ private static Color AverageInHSVSpace(IEnumerable<Color> pixels, float alphaCutoff)
98
105
  {
99
106
  float avgH = 0f;
100
107
  float avgS = 0f;
101
108
  float avgV = 0f;
102
109
  int count = 0;
103
110
 
104
- foreach (Color pixel in pixels)
111
+ foreach (Color pixel in pixels.Where(pixel => pixel.a > alphaCutoff))
105
112
  {
106
113
  Color.RGBToHSV(pixel, out float h, out float s, out float v);
107
114
 
@@ -133,7 +140,7 @@
133
140
  }
134
141
 
135
142
  // Weighted RGB averaging using perceived luminance
136
- private static Color WeightedRGBAverage(IEnumerable<Color> pixels)
143
+ private static Color WeightedRGBAverage(IEnumerable<Color> pixels, float alphaCutoff)
137
144
  {
138
145
  // Use perceived luminance weights
139
146
  const float rWeight = 0.299f;
@@ -146,7 +153,7 @@
146
153
  b = 0f,
147
154
  a = 0f;
148
155
 
149
- foreach (Color pixel in pixels)
156
+ foreach (Color pixel in pixels.Where(pixel => pixel.a > alphaCutoff))
150
157
  {
151
158
  float weight = pixel.r * rWeight + pixel.g * gWeight + pixel.b * bWeight;
152
159
  r += pixel.r * weight;
@@ -168,12 +175,12 @@
168
175
  }
169
176
 
170
177
  // Find dominant color using simple clustering
171
- private static Color GetDominantColor(IEnumerable<Color> pixels)
178
+ private static Color GetDominantColor(IEnumerable<Color> pixels, float alphaCutoff)
172
179
  {
173
180
  Dictionary<Vector3Int, int> colorBuckets = new();
174
181
  const int bucketSize = 32; // Adjust for different precision
175
182
 
176
- foreach (Color pixel in pixels)
183
+ foreach (Color pixel in pixels.Where(pixel => pixel.a > alphaCutoff))
177
184
  {
178
185
  Vector3Int bucket = new(
179
186
  Mathf.RoundToInt(pixel.r * 255 / bucketSize),
@@ -1,5 +1,8 @@
1
1
  namespace UnityHelpers.Core.Extension
2
2
  {
3
+ using System;
4
+ using System.Collections.Generic;
5
+ using System.Linq;
3
6
  using Helper;
4
7
  using Random;
5
8
  using UnityEngine;
@@ -11,6 +14,33 @@
11
14
  return random.NextVector2(-amplitude, amplitude);
12
15
  }
13
16
 
17
+ public static T NextEnumExcept<T>(this IRandom random, params T[] values)
18
+ where T : struct, Enum
19
+ {
20
+ T value;
21
+ do
22
+ {
23
+ value = random.NextEnum<T>();
24
+ } while (values.Contains(value));
25
+
26
+ return value;
27
+ }
28
+
29
+ public static T NextOfExcept<T>(
30
+ this IRandom random,
31
+ IEnumerable<T> values,
32
+ params T[] exceptions
33
+ )
34
+ {
35
+ T value;
36
+ do
37
+ {
38
+ value = random.NextOf(values);
39
+ } while (exceptions.Contains(value));
40
+
41
+ return value;
42
+ }
43
+
14
44
  public static Vector2 NextVector2(
15
45
  this IRandom random,
16
46
  float minAmplitude,
@@ -417,16 +417,40 @@
417
417
 
418
418
  public Guid NextGuid()
419
419
  {
420
- byte[] guidBytes = new byte[16];
421
- NextBytes(guidBytes);
422
- return new Guid(guidBytes);
420
+ return new Guid(GenerateGuidBytes());
423
421
  }
424
422
 
425
423
  public KGuid NextKGuid()
424
+ {
425
+ return new KGuid(GenerateGuidBytes());
426
+ }
427
+
428
+ private byte[] GenerateGuidBytes()
426
429
  {
427
430
  byte[] guidBytes = new byte[16];
428
431
  NextBytes(guidBytes);
429
- return new KGuid(guidBytes);
432
+ SetUuidV4Bits(guidBytes);
433
+ return guidBytes;
434
+ }
435
+
436
+ public static void SetUuidV4Bits(byte[] bytes)
437
+ {
438
+ // Set version to 4 (bits 6-7 of byte 6)
439
+
440
+ // Clear the version bits first (clear bits 4-7)
441
+ byte value = bytes[6];
442
+ value &= 0x0f;
443
+ // Set version 4 (set bits 4-7 to 0100)
444
+ value |= 0x40;
445
+ bytes[6] = value;
446
+
447
+ // Set variant to RFC 4122 (bits 6-7 of byte 8)
448
+ value = bytes[8];
449
+ // Clear the variant bits first (clear bits 6-7)
450
+ value &= 0x3f;
451
+ // Set RFC 4122 variant (set bits 6-7 to 10)
452
+ value |= 0x80;
453
+ bytes[8] = value;
430
454
  }
431
455
 
432
456
  // Advances the RNG
@@ -549,6 +549,26 @@
549
549
  }
550
550
  }
551
551
 
552
+ [Test]
553
+ [Parallelizable]
554
+ public void NextEnumerableExcept()
555
+ {
556
+ IRandom random = NewRandom();
557
+ for (int i = 0; i < NormalIterations; ++i)
558
+ {
559
+ TestValues exception = random.NextEnum<TestValues>();
560
+ HashSet<TestValues> selected = Enum.GetValues(typeof(TestValues))
561
+ .OfType<TestValues>()
562
+ .Shuffled(random)
563
+ .Skip(3)
564
+ .ToHashSet();
565
+
566
+ TestValues value = random.NextOfExcept(selected.Shuffled(random), exception);
567
+ Assert.IsTrue(selected.Contains(value));
568
+ Assert.AreNotEqual(value, exception);
569
+ }
570
+ }
571
+
552
572
  [Test]
553
573
  [Parallelizable]
554
574
  public void NextArray()
@@ -636,6 +656,23 @@
636
656
  Assert.AreEqual(Enum.GetValues(typeof(TestValues)).Length, seenEnums.Count);
637
657
  }
638
658
 
659
+ [Test]
660
+ [Parallelizable]
661
+ public void NextEnumExcept()
662
+ {
663
+ IRandom random = NewRandom();
664
+ HashSet<TestValues> seenEnums = new();
665
+ for (int i = 0; i < NormalIterations; ++i)
666
+ {
667
+ TestValues value = random.NextEnumExcept(TestValues.Value8, TestValues.Value9);
668
+ _ = seenEnums.Add(value);
669
+ }
670
+
671
+ Assert.AreEqual(Enum.GetValues(typeof(TestValues)).Length - 2, seenEnums.Count);
672
+ Assert.IsFalse(seenEnums.Contains(TestValues.Value8));
673
+ Assert.IsFalse(seenEnums.Contains(TestValues.Value9));
674
+ }
675
+
639
676
  [Test]
640
677
  [Parallelizable]
641
678
  public void NextNoiseMap()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "com.wallstop-studios.unity-helpers",
3
- "version": "2.0.0-rc14",
3
+ "version": "2.0.0-rc16",
4
4
  "displayName": "Unity Helpers",
5
5
  "description": "Various Unity Helper Library",
6
6
  "dependencies": {},