com.wallstop-studios.unity-helpers 2.0.0-rc13 → 2.0.0-rc15
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/Editor/EnsureTextureSizeWizard.cs +110 -0
- package/Editor/EnsureTextureSizeWizard.cs.meta +3 -0
- 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 +267 -1
- package/Runtime/Core/Extension/UnityExtensions.cs +10 -0
- package/Runtime/Core/Helper/Geometry.cs +17 -0
- package/Runtime/Core/Helper/SpriteHelpers.cs +36 -3
- package/Runtime/Core/Random/PcgRandom.cs +2 -2
- package/Runtime/Core/Random/RomuDuo.cs +116 -0
- package/Runtime/Core/Random/RomuDuo.cs.meta +3 -0
- package/Runtime/Core/Random/SplitMix64.cs +94 -0
- package/Runtime/Core/Random/SplitMix64.cs.meta +3 -0
- package/Runtime/Core/Random/XorShiroRandom.cs +117 -0
- package/Runtime/Core/Random/XorShiroRandom.cs.meta +3 -0
- package/Runtime/UI/LayeredImage.cs +364 -0
- package/Runtime/UI/LayeredImage.cs.meta +3 -0
- package/Runtime/UI.meta +3 -0
- package/Tests/Runtime/Performance/RandomPerformanceTests.cs +3 -0
- package/Tests/Runtime/Random/RomuDuoRandomTests.cs +9 -0
- package/Tests/Runtime/Random/RomuDuoRandomTests.cs.meta +3 -0
- package/Tests/Runtime/Random/SplitMix64RandomTests.cs +9 -0
- package/Tests/Runtime/Random/SplitMix64RandomTests.cs.meta +3 -0
- package/Tests/Runtime/Random/XorShiroRandomTests.cs +9 -0
- package/Tests/Runtime/Random/XorShiroRandomTests.cs.meta +3 -0
- package/package.json +1 -1
|
@@ -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
|
}
|
|
@@ -1,13 +1,279 @@
|
|
|
1
1
|
namespace UnityHelpers.Core.Extension
|
|
2
2
|
{
|
|
3
3
|
using System;
|
|
4
|
+
using System.Collections.Generic;
|
|
5
|
+
using System.ComponentModel;
|
|
6
|
+
using System.Linq;
|
|
7
|
+
using Helper;
|
|
4
8
|
using Random;
|
|
5
9
|
using UnityEngine;
|
|
6
10
|
|
|
11
|
+
public enum ColorAveragingMethod
|
|
12
|
+
{
|
|
13
|
+
LAB = 0, // CIE L*a*b* space averaging
|
|
14
|
+
HSV = 1, // HSV space averaging
|
|
15
|
+
Weighted = 2, // Weighted RGB averaging using perceived luminance
|
|
16
|
+
Dominant = 3, // Find most dominant color cluster
|
|
17
|
+
}
|
|
18
|
+
|
|
7
19
|
// https://sharpsnippets.wordpress.com/2014/03/11/c-extension-complementary-color/
|
|
8
20
|
public static class ColorExtensions
|
|
9
21
|
{
|
|
10
|
-
public static Color
|
|
22
|
+
public static Color GetAverageColor(
|
|
23
|
+
this Sprite sprite,
|
|
24
|
+
ColorAveragingMethod method = ColorAveragingMethod.LAB,
|
|
25
|
+
float alphaCutoff = 0.01f
|
|
26
|
+
)
|
|
27
|
+
{
|
|
28
|
+
return GetAverageColor(Enumerables.Of(sprite), method, alphaCutoff);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public static Color GetAverageColor(
|
|
32
|
+
this IEnumerable<Sprite> sprites,
|
|
33
|
+
ColorAveragingMethod method = ColorAveragingMethod.LAB,
|
|
34
|
+
float alphaCutoff = 0.01f
|
|
35
|
+
)
|
|
36
|
+
{
|
|
37
|
+
return GetAverageColor(
|
|
38
|
+
sprites
|
|
39
|
+
.Where(Objects.NotNull)
|
|
40
|
+
.Select(sprite => sprite.texture)
|
|
41
|
+
.Where(Objects.NotNull)
|
|
42
|
+
.SelectMany(texture =>
|
|
43
|
+
{
|
|
44
|
+
texture.MakeReadable();
|
|
45
|
+
Color[] pixels = texture.GetPixels();
|
|
46
|
+
return pixels;
|
|
47
|
+
}),
|
|
48
|
+
method,
|
|
49
|
+
alphaCutoff
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public static Color GetAverageColor(
|
|
54
|
+
this IEnumerable<Color> pixels,
|
|
55
|
+
ColorAveragingMethod method = ColorAveragingMethod.LAB,
|
|
56
|
+
float alphaCutoff = 0.01f
|
|
57
|
+
)
|
|
58
|
+
{
|
|
59
|
+
switch (method)
|
|
60
|
+
{
|
|
61
|
+
case ColorAveragingMethod.LAB:
|
|
62
|
+
{
|
|
63
|
+
return AverageInLABSpace(pixels, alphaCutoff);
|
|
64
|
+
}
|
|
65
|
+
case ColorAveragingMethod.HSV:
|
|
66
|
+
{
|
|
67
|
+
return AverageInHSVSpace(pixels, alphaCutoff);
|
|
68
|
+
}
|
|
69
|
+
case ColorAveragingMethod.Weighted:
|
|
70
|
+
{
|
|
71
|
+
return WeightedRGBAverage(pixels, alphaCutoff);
|
|
72
|
+
}
|
|
73
|
+
case ColorAveragingMethod.Dominant:
|
|
74
|
+
{
|
|
75
|
+
return GetDominantColor(pixels, alphaCutoff);
|
|
76
|
+
}
|
|
77
|
+
default:
|
|
78
|
+
{
|
|
79
|
+
throw new InvalidEnumArgumentException(
|
|
80
|
+
nameof(method),
|
|
81
|
+
(int)method,
|
|
82
|
+
typeof(ColorAveragingMethod)
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// CIE L*a*b* space averaging - most perceptually accurate
|
|
89
|
+
private static Color AverageInLABSpace(IEnumerable<Color> pixels, float alphaCutoff)
|
|
90
|
+
{
|
|
91
|
+
List<LABColor> labValues = pixels
|
|
92
|
+
.Where(pixel => pixel.a > alphaCutoff)
|
|
93
|
+
.Select(RGBToLAB)
|
|
94
|
+
.ToList();
|
|
95
|
+
|
|
96
|
+
double avgL = labValues.Average(lab => lab.l);
|
|
97
|
+
double avgA = labValues.Average(lab => lab.a);
|
|
98
|
+
double avgB = labValues.Average(lab => lab.b);
|
|
99
|
+
|
|
100
|
+
return LABToRGB(avgL, avgA, avgB);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// HSV space averaging - good for preserving vibrant colors
|
|
104
|
+
private static Color AverageInHSVSpace(IEnumerable<Color> pixels, float alphaCutoff)
|
|
105
|
+
{
|
|
106
|
+
float avgH = 0f;
|
|
107
|
+
float avgS = 0f;
|
|
108
|
+
float avgV = 0f;
|
|
109
|
+
int count = 0;
|
|
110
|
+
|
|
111
|
+
foreach (Color pixel in pixels.Where(pixel => pixel.a > alphaCutoff))
|
|
112
|
+
{
|
|
113
|
+
Color.RGBToHSV(pixel, out float h, out float s, out float v);
|
|
114
|
+
|
|
115
|
+
// Handle hue wrapping around 360 degrees
|
|
116
|
+
float hRad = h * 2f * Mathf.PI;
|
|
117
|
+
avgH += Mathf.Cos(hRad);
|
|
118
|
+
avgH += Mathf.Sin(hRad);
|
|
119
|
+
|
|
120
|
+
avgS += s;
|
|
121
|
+
avgV += v;
|
|
122
|
+
count++;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
avgH = Mathf.Atan2(avgH / count, avgH / count) / (2f * Mathf.PI);
|
|
126
|
+
if (avgH < 0)
|
|
127
|
+
{
|
|
128
|
+
avgH += 1f;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (count <= 0)
|
|
132
|
+
{
|
|
133
|
+
count = 1;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
avgS /= count;
|
|
137
|
+
avgV /= count;
|
|
138
|
+
|
|
139
|
+
return Color.HSVToRGB(avgH, avgS, avgV);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Weighted RGB averaging using perceived luminance
|
|
143
|
+
private static Color WeightedRGBAverage(IEnumerable<Color> pixels, float alphaCutoff)
|
|
144
|
+
{
|
|
145
|
+
// Use perceived luminance weights
|
|
146
|
+
const float rWeight = 0.299f;
|
|
147
|
+
const float gWeight = 0.587f;
|
|
148
|
+
const float bWeight = 0.114f;
|
|
149
|
+
|
|
150
|
+
float totalWeight = 0f;
|
|
151
|
+
float r = 0f,
|
|
152
|
+
g = 0f,
|
|
153
|
+
b = 0f,
|
|
154
|
+
a = 0f;
|
|
155
|
+
|
|
156
|
+
foreach (Color pixel in pixels.Where(pixel => pixel.a > alphaCutoff))
|
|
157
|
+
{
|
|
158
|
+
float weight = pixel.r * rWeight + pixel.g * gWeight + pixel.b * bWeight;
|
|
159
|
+
r += pixel.r * weight;
|
|
160
|
+
g += pixel.g * weight;
|
|
161
|
+
b += pixel.b * weight;
|
|
162
|
+
a += pixel.a * weight;
|
|
163
|
+
totalWeight += weight;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (totalWeight > 0f)
|
|
167
|
+
{
|
|
168
|
+
r /= totalWeight;
|
|
169
|
+
g /= totalWeight;
|
|
170
|
+
b /= totalWeight;
|
|
171
|
+
a /= totalWeight;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return new Color(r, g, b, a);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Find dominant color using simple clustering
|
|
178
|
+
private static Color GetDominantColor(IEnumerable<Color> pixels, float alphaCutoff)
|
|
179
|
+
{
|
|
180
|
+
Dictionary<Vector3Int, int> colorBuckets = new();
|
|
181
|
+
const int bucketSize = 32; // Adjust for different precision
|
|
182
|
+
|
|
183
|
+
foreach (Color pixel in pixels.Where(pixel => pixel.a > alphaCutoff))
|
|
184
|
+
{
|
|
185
|
+
Vector3Int bucket = new(
|
|
186
|
+
Mathf.RoundToInt(pixel.r * 255 / bucketSize),
|
|
187
|
+
Mathf.RoundToInt(pixel.g * 255 / bucketSize),
|
|
188
|
+
Mathf.RoundToInt(pixel.b * 255 / bucketSize)
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
colorBuckets.TryAdd(bucket, 0);
|
|
192
|
+
colorBuckets[bucket]++;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
Vector3Int dominantBucket = colorBuckets
|
|
196
|
+
.OrderByDescending(kvp => kvp.Value)
|
|
197
|
+
.First()
|
|
198
|
+
.Key;
|
|
199
|
+
return new Color(
|
|
200
|
+
dominantBucket.x * bucketSize / 255f,
|
|
201
|
+
dominantBucket.y * bucketSize / 255f,
|
|
202
|
+
dominantBucket.z * bucketSize / 255f
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Helper struct for LAB color space
|
|
207
|
+
private readonly struct LABColor
|
|
208
|
+
{
|
|
209
|
+
public readonly double l;
|
|
210
|
+
public readonly double a;
|
|
211
|
+
public readonly double b;
|
|
212
|
+
|
|
213
|
+
public LABColor(double l, double a, double b)
|
|
214
|
+
{
|
|
215
|
+
this.l = l;
|
|
216
|
+
this.a = a;
|
|
217
|
+
this.b = b;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private static LABColor RGBToLAB(Color rgb)
|
|
222
|
+
{
|
|
223
|
+
// First convert to XYZ
|
|
224
|
+
double r =
|
|
225
|
+
rgb.r > 0.04045 ? Mathf.Pow((rgb.r + 0.055f) / 1.055f, 2.4f) : rgb.r / 12.92f;
|
|
226
|
+
double g =
|
|
227
|
+
rgb.g > 0.04045 ? Mathf.Pow((rgb.g + 0.055f) / 1.055f, 2.4f) : rgb.g / 12.92f;
|
|
228
|
+
double b =
|
|
229
|
+
rgb.b > 0.04045 ? Mathf.Pow((rgb.b + 0.055f) / 1.055f, 2.4f) : rgb.b / 12.92f;
|
|
230
|
+
|
|
231
|
+
double x = (r * 0.4124 + g * 0.3576 + b * 0.1805) / 0.95047;
|
|
232
|
+
double y = (r * 0.2126 + g * 0.7152 + b * 0.0722);
|
|
233
|
+
double z = (r * 0.0193 + g * 0.1192 + b * 0.9505) / 1.08883;
|
|
234
|
+
|
|
235
|
+
x = x > 0.008856 ? Mathf.Pow((float)x, 1f / 3f) : (7.787 * x) + 16f / 116f;
|
|
236
|
+
y = y > 0.008856 ? Mathf.Pow((float)y, 1f / 3f) : (7.787 * y) + 16f / 116f;
|
|
237
|
+
z = z > 0.008856 ? Mathf.Pow((float)z, 1f / 3f) : (7.787 * z) + 16f / 116f;
|
|
238
|
+
|
|
239
|
+
return new LABColor((116 * y) - 16, 500 * (x - y), 200 * (y - z));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private static Color LABToRGB(double l, double a, double b)
|
|
243
|
+
{
|
|
244
|
+
double y = (l + 16) / 116;
|
|
245
|
+
double x = a / 500 + y;
|
|
246
|
+
double z = y - b / 200;
|
|
247
|
+
|
|
248
|
+
double x3 = x * x * x;
|
|
249
|
+
double y3 = y * y * y;
|
|
250
|
+
double z3 = z * z * z;
|
|
251
|
+
|
|
252
|
+
x = 0.95047 * (x3 > 0.008856 ? x3 : (x - 16.0 / 116.0) / 7.787);
|
|
253
|
+
y = y3 > 0.008856 ? y3 : (y - 16.0 / 116.0) / 7.787;
|
|
254
|
+
z = 1.08883 * (z3 > 0.008856 ? z3 : (z - 16.0 / 116.0) / 7.787);
|
|
255
|
+
|
|
256
|
+
double r = x * 3.2406 + y * -1.5372 + z * -0.4986;
|
|
257
|
+
double g = x * -0.9689 + y * 1.8758 + z * 0.0415;
|
|
258
|
+
double b2 = x * 0.0557 + y * -0.2040 + z * 1.0570;
|
|
259
|
+
|
|
260
|
+
r = r > 0.0031308 ? 1.055 * Mathf.Pow((float)r, 1 / 2.4f) - 0.055 : 12.92 * r;
|
|
261
|
+
g = g > 0.0031308 ? 1.055 * Mathf.Pow((float)g, 1 / 2.4f) - 0.055 : 12.92 * g;
|
|
262
|
+
b2 = b2 > 0.0031308 ? 1.055 * Mathf.Pow((float)b2, 1 / 2.4f) - 0.055 : 12.92 * b2;
|
|
263
|
+
|
|
264
|
+
return new Color(
|
|
265
|
+
Mathf.Clamp01((float)r),
|
|
266
|
+
Mathf.Clamp01((float)g),
|
|
267
|
+
Mathf.Clamp01((float)b2),
|
|
268
|
+
1f
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
public static Color GetComplement(
|
|
273
|
+
this Color source,
|
|
274
|
+
IRandom random = null,
|
|
275
|
+
float variance = 0f
|
|
276
|
+
)
|
|
11
277
|
{
|
|
12
278
|
Color inputColor = source;
|
|
13
279
|
//if RGB values are close to each other by a diff less than 10%, then if RGB values are lighter side, decrease the blue by 50% (eventually it will increase in conversion below), if RBB values are on darker side, decrease yellow by about 50% (it will increase in conversion)
|
|
@@ -14,6 +14,16 @@
|
|
|
14
14
|
|
|
15
15
|
public static class UnityExtensions
|
|
16
16
|
{
|
|
17
|
+
public static Vector2 GetCenter(this GameObject gameObject)
|
|
18
|
+
{
|
|
19
|
+
if (gameObject.TryGetComponent(out CenterPointOffset centerPointOffset))
|
|
20
|
+
{
|
|
21
|
+
return centerPointOffset.CenterPoint;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return gameObject.transform.position;
|
|
25
|
+
}
|
|
26
|
+
|
|
17
27
|
public static Bounds Bounds(this Rect rect)
|
|
18
28
|
{
|
|
19
29
|
return new Bounds(rect.center, rect.size);
|
|
@@ -1,9 +1,26 @@
|
|
|
1
1
|
namespace UnityHelpers.Core.Helper
|
|
2
2
|
{
|
|
3
|
+
using System.Collections.Generic;
|
|
4
|
+
using System.Linq;
|
|
3
5
|
using UnityEngine;
|
|
4
6
|
|
|
5
7
|
public static class Geometry
|
|
6
8
|
{
|
|
9
|
+
public static Rect Accumulate(this IEnumerable<Rect> rects)
|
|
10
|
+
{
|
|
11
|
+
return rects.Aggregate(
|
|
12
|
+
(accumulated, next) =>
|
|
13
|
+
new Rect(
|
|
14
|
+
Mathf.Min(accumulated.xMin, next.xMin),
|
|
15
|
+
Mathf.Min(accumulated.yMin, next.yMin),
|
|
16
|
+
Mathf.Max(accumulated.xMax, next.xMax)
|
|
17
|
+
- Mathf.Min(accumulated.xMin, next.xMin),
|
|
18
|
+
Mathf.Max(accumulated.yMax, next.yMax)
|
|
19
|
+
- Mathf.Min(accumulated.yMin, next.yMin)
|
|
20
|
+
)
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
7
24
|
//Where is p in relation to a-b
|
|
8
25
|
// < 0 -> to the right
|
|
9
26
|
// = 0 -> on the line
|
|
@@ -1,11 +1,41 @@
|
|
|
1
1
|
namespace UnityHelpers.Core.Helper
|
|
2
2
|
{
|
|
3
|
+
using Extension;
|
|
3
4
|
using UnityEditor;
|
|
4
5
|
using UnityEngine;
|
|
5
6
|
using Utils;
|
|
6
7
|
|
|
7
8
|
public static class SpriteHelpers
|
|
8
9
|
{
|
|
10
|
+
public static void MakeReadable(this Texture2D texture)
|
|
11
|
+
{
|
|
12
|
+
if (texture.isReadable)
|
|
13
|
+
{
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
#if UNITY_EDITOR
|
|
18
|
+
string assetPath = AssetDatabase.GetAssetPath(texture);
|
|
19
|
+
if (string.IsNullOrEmpty(assetPath))
|
|
20
|
+
{
|
|
21
|
+
texture.LogError("Failed to get asset path.");
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
TextureImporter tImporter = AssetImporter.GetAtPath(assetPath) as TextureImporter;
|
|
26
|
+
if (tImporter == null)
|
|
27
|
+
{
|
|
28
|
+
texture.LogError("Failed to get texture importer.");
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
tImporter.isReadable = true;
|
|
33
|
+
EditorUtility.SetDirty(tImporter);
|
|
34
|
+
tImporter.SaveAndReimport();
|
|
35
|
+
EditorUtility.SetDirty(texture);
|
|
36
|
+
#endif
|
|
37
|
+
}
|
|
38
|
+
|
|
9
39
|
public static void SetSpritePivot(string fullSpritePath, Vector2 pivot)
|
|
10
40
|
{
|
|
11
41
|
#if UNITY_EDITOR
|
|
@@ -16,7 +46,10 @@
|
|
|
16
46
|
public static void SetSpritePivot(Sprite sprite, Vector2 pivot)
|
|
17
47
|
{
|
|
18
48
|
#if UNITY_EDITOR
|
|
19
|
-
SetSpritePivot(
|
|
49
|
+
SetSpritePivot(
|
|
50
|
+
AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(sprite)) as TextureImporter,
|
|
51
|
+
pivot
|
|
52
|
+
);
|
|
20
53
|
#endif
|
|
21
54
|
}
|
|
22
55
|
|
|
@@ -30,7 +63,7 @@
|
|
|
30
63
|
|
|
31
64
|
TextureImporterSettings textureImportSettings = new TextureImporterSettings();
|
|
32
65
|
textureImporter.ReadTextureSettings(textureImportSettings);
|
|
33
|
-
textureImportSettings.spriteAlignment = (int)
|
|
66
|
+
textureImportSettings.spriteAlignment = (int)SpriteAlignment.Custom;
|
|
34
67
|
textureImportSettings.wrapMode = TextureWrapMode.Clamp;
|
|
35
68
|
textureImportSettings.filterMode = FilterMode.Trilinear;
|
|
36
69
|
textureImporter.SetTextureSettings(textureImportSettings);
|
|
@@ -40,7 +73,7 @@
|
|
|
40
73
|
resizeAlgorithm = TextureResizeAlgorithm.Bilinear,
|
|
41
74
|
maxTextureSize = SetTextureImportData.RegularTextureSize,
|
|
42
75
|
textureCompression = TextureImporterCompression.Compressed,
|
|
43
|
-
format = TextureImporterFormat.Automatic
|
|
76
|
+
format = TextureImporterFormat.Automatic,
|
|
44
77
|
};
|
|
45
78
|
|
|
46
79
|
textureImporter.SetPlatformTextureSettings(importerSettings);
|