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 +1 -1
- package/Runtime/Core/DataStructure/KDTree.cs +123 -19
- package/Runtime/Core/DataStructure/QuadTree.cs +118 -23
- package/Runtime/Core/DataStructure/RTree.cs +140 -51
- package/Runtime/Core/Extension/ColorExtensions.cs +25 -18
- package/Runtime/Core/Extension/RandomExtensions.cs +30 -0
- package/Runtime/Core/Random/AbstractRandom.cs +28 -4
- package/Tests/Runtime/Random/RandomTestBase.cs +37 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# A Grab-Bag
|
|
2
|
-
Various Unity Helpers. Includes deterministic
|
|
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
|
-
|
|
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(
|
|
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>(
|
|
48
|
-
|
|
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>(
|
|
56
|
-
|
|
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>(
|
|
61
|
-
|
|
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(
|
|
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 =
|
|
80
|
-
|
|
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>(
|
|
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(
|
|
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 (
|
|
149
|
-
((Vector2)
|
|
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
|
-
|
|
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
|
-
|
|
22
|
+
internal readonly QuadTreeNode<V>[] children;
|
|
22
23
|
public readonly V[] elements;
|
|
23
24
|
public readonly bool isTerminal;
|
|
24
25
|
|
|
25
|
-
public QuadTreeNode(
|
|
26
|
-
|
|
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(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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>(
|
|
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(
|
|
77
|
-
|
|
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 =
|
|
80
|
-
|
|
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>(
|
|
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(
|
|
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 =
|
|
146
|
-
|
|
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
|
-
|
|
15
|
+
public sealed class RTreeNode<V>
|
|
15
16
|
{
|
|
16
17
|
public readonly Bounds boundary;
|
|
17
|
-
|
|
18
|
+
internal readonly RTreeNode<V>[] children;
|
|
18
19
|
public readonly V[] elements;
|
|
19
20
|
public readonly bool isTerminal;
|
|
20
21
|
|
|
21
|
-
public RTreeNode(
|
|
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 =
|
|
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 (
|
|
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 (
|
|
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(
|
|
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,
|
|
101
|
-
|
|
128
|
+
IEnumerable<T> points,
|
|
129
|
+
Func<T, Bounds> elementTransformer,
|
|
130
|
+
int bucketSize = DefaultBucketSize,
|
|
131
|
+
int branchFactor = DefaultBranchFactor
|
|
132
|
+
)
|
|
102
133
|
{
|
|
103
|
-
_elementTransformer =
|
|
104
|
-
|
|
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>(
|
|
139
|
+
_head = new RTreeNode<T>(
|
|
140
|
+
elements.ToList(),
|
|
141
|
+
elementTransformer,
|
|
142
|
+
bucketSize,
|
|
143
|
+
branchFactor
|
|
144
|
+
);
|
|
107
145
|
}
|
|
108
146
|
|
|
109
|
-
public IEnumerable<T> GetElementsInRange(
|
|
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
|
-
|
|
120
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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()
|