com.wallstop-studios.unity-helpers 2.0.0-rc13 → 2.0.0-rc14
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/Runtime/Core/Extension/ColorExtensions.cs +260 -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
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
namespace UnityHelpers.Editor
|
|
2
|
+
{
|
|
3
|
+
using System.Collections.Generic;
|
|
4
|
+
using System.Linq;
|
|
5
|
+
using Core.Extension;
|
|
6
|
+
using UnityEditor;
|
|
7
|
+
using UnityEngine;
|
|
8
|
+
|
|
9
|
+
public sealed class EnsureTextureSizeWizard : ScriptableWizard
|
|
10
|
+
{
|
|
11
|
+
public List<Texture2D> textures = new();
|
|
12
|
+
|
|
13
|
+
public List<Object> textureSourcePaths = new();
|
|
14
|
+
|
|
15
|
+
[MenuItem("Tools/Unity Helpers/Ensure Texture Size")]
|
|
16
|
+
public static void EnsureSizes()
|
|
17
|
+
{
|
|
18
|
+
_ = DisplayWizard<EnsureTextureSizeWizard>("Ensure Texture Size", "Run");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
private void OnWizardCreate()
|
|
22
|
+
{
|
|
23
|
+
textures ??= new List<Texture2D>();
|
|
24
|
+
textureSourcePaths ??= new List<Object>();
|
|
25
|
+
HashSet<string> texturePath = new();
|
|
26
|
+
foreach (Object textureSource in textureSourcePaths)
|
|
27
|
+
{
|
|
28
|
+
string assetPath = AssetDatabase.GetAssetPath(textureSource);
|
|
29
|
+
if (!string.IsNullOrWhiteSpace(assetPath))
|
|
30
|
+
{
|
|
31
|
+
_ = texturePath.Add(assetPath);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (texturePath.Any())
|
|
36
|
+
{
|
|
37
|
+
foreach (
|
|
38
|
+
string assetGuid in AssetDatabase.FindAssets(
|
|
39
|
+
"t:texture2D",
|
|
40
|
+
texturePath.ToArray()
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
{
|
|
44
|
+
string path = AssetDatabase.GUIDToAssetPath(assetGuid);
|
|
45
|
+
if (string.IsNullOrWhiteSpace(path))
|
|
46
|
+
{
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
Texture2D texture = AssetDatabase.LoadAssetAtPath<Texture2D>(path);
|
|
51
|
+
if (texture != null)
|
|
52
|
+
{
|
|
53
|
+
textures.Add(texture);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
textures = textures.Distinct().OrderBy(texture => texture.name).ToList();
|
|
59
|
+
if (textures.Count <= 0)
|
|
60
|
+
{
|
|
61
|
+
this.Log("Failed to find any texture paths.");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
int changedCount = 0;
|
|
66
|
+
foreach (Texture2D inputTexture in textures)
|
|
67
|
+
{
|
|
68
|
+
Texture2D texture = inputTexture;
|
|
69
|
+
string assetPath = AssetDatabase.GetAssetPath(texture);
|
|
70
|
+
if (string.IsNullOrWhiteSpace(assetPath))
|
|
71
|
+
{
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
TextureImporter tImporter = AssetImporter.GetAtPath(assetPath) as TextureImporter;
|
|
76
|
+
if (tImporter == null)
|
|
77
|
+
{
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
tImporter.GetSourceTextureWidthAndHeight(out int width, out int height);
|
|
81
|
+
|
|
82
|
+
float size = Mathf.Max(width, height);
|
|
83
|
+
int textureSize = tImporter.maxTextureSize;
|
|
84
|
+
bool changed = false;
|
|
85
|
+
while (textureSize < size)
|
|
86
|
+
{
|
|
87
|
+
changed = true;
|
|
88
|
+
textureSize <<= 1;
|
|
89
|
+
}
|
|
90
|
+
tImporter.maxTextureSize = textureSize;
|
|
91
|
+
|
|
92
|
+
if (changed)
|
|
93
|
+
{
|
|
94
|
+
changedCount++;
|
|
95
|
+
tImporter.SaveAndReimport();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (changedCount != 0)
|
|
100
|
+
{
|
|
101
|
+
this.Log($"Updated {changedCount} textures.");
|
|
102
|
+
AssetDatabase.Refresh();
|
|
103
|
+
}
|
|
104
|
+
else
|
|
105
|
+
{
|
|
106
|
+
this.Log("No textures updated.");
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -1,13 +1,272 @@
|
|
|
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
|
+
)
|
|
26
|
+
{
|
|
27
|
+
return GetAverageColor(Enumerables.Of(sprite), method);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public static Color GetAverageColor(
|
|
31
|
+
this IEnumerable<Sprite> sprites,
|
|
32
|
+
ColorAveragingMethod method = ColorAveragingMethod.LAB
|
|
33
|
+
)
|
|
34
|
+
{
|
|
35
|
+
return GetAverageColor(
|
|
36
|
+
sprites
|
|
37
|
+
.Where(Objects.NotNull)
|
|
38
|
+
.Select(sprite => sprite.texture)
|
|
39
|
+
.Where(Objects.NotNull)
|
|
40
|
+
.SelectMany(texture =>
|
|
41
|
+
{
|
|
42
|
+
texture.MakeReadable();
|
|
43
|
+
Color[] pixels = texture.GetPixels();
|
|
44
|
+
return pixels.Where(pixel => pixel.a > 0.01f);
|
|
45
|
+
}),
|
|
46
|
+
method
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public static Color GetAverageColor(
|
|
51
|
+
this IEnumerable<Color> pixels,
|
|
52
|
+
ColorAveragingMethod method = ColorAveragingMethod.LAB
|
|
53
|
+
)
|
|
54
|
+
{
|
|
55
|
+
switch (method)
|
|
56
|
+
{
|
|
57
|
+
case ColorAveragingMethod.LAB:
|
|
58
|
+
{
|
|
59
|
+
return AverageInLABSpace(pixels);
|
|
60
|
+
}
|
|
61
|
+
case ColorAveragingMethod.HSV:
|
|
62
|
+
{
|
|
63
|
+
return AverageInHSVSpace(pixels);
|
|
64
|
+
}
|
|
65
|
+
case ColorAveragingMethod.Weighted:
|
|
66
|
+
{
|
|
67
|
+
return WeightedRGBAverage(pixels);
|
|
68
|
+
}
|
|
69
|
+
case ColorAveragingMethod.Dominant:
|
|
70
|
+
{
|
|
71
|
+
return GetDominantColor(pixels);
|
|
72
|
+
}
|
|
73
|
+
default:
|
|
74
|
+
{
|
|
75
|
+
throw new InvalidEnumArgumentException(
|
|
76
|
+
nameof(method),
|
|
77
|
+
(int)method,
|
|
78
|
+
typeof(ColorAveragingMethod)
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// CIE L*a*b* space averaging - most perceptually accurate
|
|
85
|
+
private static Color AverageInLABSpace(IEnumerable<Color> pixels)
|
|
86
|
+
{
|
|
87
|
+
List<LABColor> labValues = pixels.Select(RGBToLAB).ToList();
|
|
88
|
+
|
|
89
|
+
double avgL = labValues.Average(lab => lab.l);
|
|
90
|
+
double avgA = labValues.Average(lab => lab.a);
|
|
91
|
+
double avgB = labValues.Average(lab => lab.b);
|
|
92
|
+
|
|
93
|
+
return LABToRGB(avgL, avgA, avgB);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// HSV space averaging - good for preserving vibrant colors
|
|
97
|
+
private static Color AverageInHSVSpace(IEnumerable<Color> pixels)
|
|
98
|
+
{
|
|
99
|
+
float avgH = 0f;
|
|
100
|
+
float avgS = 0f;
|
|
101
|
+
float avgV = 0f;
|
|
102
|
+
int count = 0;
|
|
103
|
+
|
|
104
|
+
foreach (Color pixel in pixels)
|
|
105
|
+
{
|
|
106
|
+
Color.RGBToHSV(pixel, out float h, out float s, out float v);
|
|
107
|
+
|
|
108
|
+
// Handle hue wrapping around 360 degrees
|
|
109
|
+
float hRad = h * 2f * Mathf.PI;
|
|
110
|
+
avgH += Mathf.Cos(hRad);
|
|
111
|
+
avgH += Mathf.Sin(hRad);
|
|
112
|
+
|
|
113
|
+
avgS += s;
|
|
114
|
+
avgV += v;
|
|
115
|
+
count++;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
avgH = Mathf.Atan2(avgH / count, avgH / count) / (2f * Mathf.PI);
|
|
119
|
+
if (avgH < 0)
|
|
120
|
+
{
|
|
121
|
+
avgH += 1f;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (count <= 0)
|
|
125
|
+
{
|
|
126
|
+
count = 1;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
avgS /= count;
|
|
130
|
+
avgV /= count;
|
|
131
|
+
|
|
132
|
+
return Color.HSVToRGB(avgH, avgS, avgV);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Weighted RGB averaging using perceived luminance
|
|
136
|
+
private static Color WeightedRGBAverage(IEnumerable<Color> pixels)
|
|
137
|
+
{
|
|
138
|
+
// Use perceived luminance weights
|
|
139
|
+
const float rWeight = 0.299f;
|
|
140
|
+
const float gWeight = 0.587f;
|
|
141
|
+
const float bWeight = 0.114f;
|
|
142
|
+
|
|
143
|
+
float totalWeight = 0f;
|
|
144
|
+
float r = 0f,
|
|
145
|
+
g = 0f,
|
|
146
|
+
b = 0f,
|
|
147
|
+
a = 0f;
|
|
148
|
+
|
|
149
|
+
foreach (Color pixel in pixels)
|
|
150
|
+
{
|
|
151
|
+
float weight = pixel.r * rWeight + pixel.g * gWeight + pixel.b * bWeight;
|
|
152
|
+
r += pixel.r * weight;
|
|
153
|
+
g += pixel.g * weight;
|
|
154
|
+
b += pixel.b * weight;
|
|
155
|
+
a += pixel.a * weight;
|
|
156
|
+
totalWeight += weight;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (totalWeight > 0f)
|
|
160
|
+
{
|
|
161
|
+
r /= totalWeight;
|
|
162
|
+
g /= totalWeight;
|
|
163
|
+
b /= totalWeight;
|
|
164
|
+
a /= totalWeight;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return new Color(r, g, b, a);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Find dominant color using simple clustering
|
|
171
|
+
private static Color GetDominantColor(IEnumerable<Color> pixels)
|
|
172
|
+
{
|
|
173
|
+
Dictionary<Vector3Int, int> colorBuckets = new();
|
|
174
|
+
const int bucketSize = 32; // Adjust for different precision
|
|
175
|
+
|
|
176
|
+
foreach (Color pixel in pixels)
|
|
177
|
+
{
|
|
178
|
+
Vector3Int bucket = new(
|
|
179
|
+
Mathf.RoundToInt(pixel.r * 255 / bucketSize),
|
|
180
|
+
Mathf.RoundToInt(pixel.g * 255 / bucketSize),
|
|
181
|
+
Mathf.RoundToInt(pixel.b * 255 / bucketSize)
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
colorBuckets.TryAdd(bucket, 0);
|
|
185
|
+
colorBuckets[bucket]++;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
Vector3Int dominantBucket = colorBuckets
|
|
189
|
+
.OrderByDescending(kvp => kvp.Value)
|
|
190
|
+
.First()
|
|
191
|
+
.Key;
|
|
192
|
+
return new Color(
|
|
193
|
+
dominantBucket.x * bucketSize / 255f,
|
|
194
|
+
dominantBucket.y * bucketSize / 255f,
|
|
195
|
+
dominantBucket.z * bucketSize / 255f
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Helper struct for LAB color space
|
|
200
|
+
private readonly struct LABColor
|
|
201
|
+
{
|
|
202
|
+
public readonly double l;
|
|
203
|
+
public readonly double a;
|
|
204
|
+
public readonly double b;
|
|
205
|
+
|
|
206
|
+
public LABColor(double l, double a, double b)
|
|
207
|
+
{
|
|
208
|
+
this.l = l;
|
|
209
|
+
this.a = a;
|
|
210
|
+
this.b = b;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private static LABColor RGBToLAB(Color rgb)
|
|
215
|
+
{
|
|
216
|
+
// First convert to XYZ
|
|
217
|
+
double r =
|
|
218
|
+
rgb.r > 0.04045 ? Mathf.Pow((rgb.r + 0.055f) / 1.055f, 2.4f) : rgb.r / 12.92f;
|
|
219
|
+
double g =
|
|
220
|
+
rgb.g > 0.04045 ? Mathf.Pow((rgb.g + 0.055f) / 1.055f, 2.4f) : rgb.g / 12.92f;
|
|
221
|
+
double b =
|
|
222
|
+
rgb.b > 0.04045 ? Mathf.Pow((rgb.b + 0.055f) / 1.055f, 2.4f) : rgb.b / 12.92f;
|
|
223
|
+
|
|
224
|
+
double x = (r * 0.4124 + g * 0.3576 + b * 0.1805) / 0.95047;
|
|
225
|
+
double y = (r * 0.2126 + g * 0.7152 + b * 0.0722);
|
|
226
|
+
double z = (r * 0.0193 + g * 0.1192 + b * 0.9505) / 1.08883;
|
|
227
|
+
|
|
228
|
+
x = x > 0.008856 ? Mathf.Pow((float)x, 1f / 3f) : (7.787 * x) + 16f / 116f;
|
|
229
|
+
y = y > 0.008856 ? Mathf.Pow((float)y, 1f / 3f) : (7.787 * y) + 16f / 116f;
|
|
230
|
+
z = z > 0.008856 ? Mathf.Pow((float)z, 1f / 3f) : (7.787 * z) + 16f / 116f;
|
|
231
|
+
|
|
232
|
+
return new LABColor((116 * y) - 16, 500 * (x - y), 200 * (y - z));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private static Color LABToRGB(double l, double a, double b)
|
|
236
|
+
{
|
|
237
|
+
double y = (l + 16) / 116;
|
|
238
|
+
double x = a / 500 + y;
|
|
239
|
+
double z = y - b / 200;
|
|
240
|
+
|
|
241
|
+
double x3 = x * x * x;
|
|
242
|
+
double y3 = y * y * y;
|
|
243
|
+
double z3 = z * z * z;
|
|
244
|
+
|
|
245
|
+
x = 0.95047 * (x3 > 0.008856 ? x3 : (x - 16.0 / 116.0) / 7.787);
|
|
246
|
+
y = y3 > 0.008856 ? y3 : (y - 16.0 / 116.0) / 7.787;
|
|
247
|
+
z = 1.08883 * (z3 > 0.008856 ? z3 : (z - 16.0 / 116.0) / 7.787);
|
|
248
|
+
|
|
249
|
+
double r = x * 3.2406 + y * -1.5372 + z * -0.4986;
|
|
250
|
+
double g = x * -0.9689 + y * 1.8758 + z * 0.0415;
|
|
251
|
+
double b2 = x * 0.0557 + y * -0.2040 + z * 1.0570;
|
|
252
|
+
|
|
253
|
+
r = r > 0.0031308 ? 1.055 * Mathf.Pow((float)r, 1 / 2.4f) - 0.055 : 12.92 * r;
|
|
254
|
+
g = g > 0.0031308 ? 1.055 * Mathf.Pow((float)g, 1 / 2.4f) - 0.055 : 12.92 * g;
|
|
255
|
+
b2 = b2 > 0.0031308 ? 1.055 * Mathf.Pow((float)b2, 1 / 2.4f) - 0.055 : 12.92 * b2;
|
|
256
|
+
|
|
257
|
+
return new Color(
|
|
258
|
+
Mathf.Clamp01((float)r),
|
|
259
|
+
Mathf.Clamp01((float)g),
|
|
260
|
+
Mathf.Clamp01((float)b2),
|
|
261
|
+
1f
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
public static Color GetComplement(
|
|
266
|
+
this Color source,
|
|
267
|
+
IRandom random = null,
|
|
268
|
+
float variance = 0f
|
|
269
|
+
)
|
|
11
270
|
{
|
|
12
271
|
Color inputColor = source;
|
|
13
272
|
//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);
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
{
|
|
18
18
|
public static IRandom Instance => ThreadLocalRandom<PcgRandom>.Instance;
|
|
19
19
|
|
|
20
|
+
public override RandomState InternalState => new(_state, _increment, _cachedGaussian);
|
|
21
|
+
|
|
20
22
|
internal readonly ulong _increment;
|
|
21
23
|
|
|
22
24
|
internal ulong _state;
|
|
@@ -53,8 +55,6 @@
|
|
|
53
55
|
_increment = NextUlong();
|
|
54
56
|
}
|
|
55
57
|
|
|
56
|
-
public override RandomState InternalState => new(_state, _increment, _cachedGaussian);
|
|
57
|
-
|
|
58
58
|
public override uint NextUint()
|
|
59
59
|
{
|
|
60
60
|
unchecked
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
namespace UnityHelpers.Core.Random
|
|
2
|
+
{
|
|
3
|
+
using System;
|
|
4
|
+
using System.Runtime.CompilerServices;
|
|
5
|
+
using System.Runtime.Serialization;
|
|
6
|
+
using System.Text.Json.Serialization;
|
|
7
|
+
using Extension;
|
|
8
|
+
using Helper;
|
|
9
|
+
|
|
10
|
+
[Serializable]
|
|
11
|
+
[DataContract]
|
|
12
|
+
public sealed class RomuDuo
|
|
13
|
+
: AbstractRandom,
|
|
14
|
+
IEquatable<RomuDuo>,
|
|
15
|
+
IComparable,
|
|
16
|
+
IComparable<RomuDuo>
|
|
17
|
+
{
|
|
18
|
+
public static IRandom Instance => ThreadLocalRandom<RomuDuo>.Instance;
|
|
19
|
+
public override RandomState InternalState => new(_x, _y, _cachedGaussian);
|
|
20
|
+
|
|
21
|
+
internal ulong _x;
|
|
22
|
+
internal ulong _y;
|
|
23
|
+
|
|
24
|
+
public RomuDuo()
|
|
25
|
+
: this(Guid.NewGuid()) { }
|
|
26
|
+
|
|
27
|
+
public RomuDuo(Guid guid)
|
|
28
|
+
{
|
|
29
|
+
byte[] bytes = guid.ToByteArray();
|
|
30
|
+
_x = BitConverter.ToUInt64(bytes, 0);
|
|
31
|
+
_y = BitConverter.ToUInt64(bytes, sizeof(ulong));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public RomuDuo(ulong seedX, ulong seedY)
|
|
35
|
+
{
|
|
36
|
+
_x = seedX;
|
|
37
|
+
_y = seedY;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
[JsonConstructor]
|
|
41
|
+
public RomuDuo(RandomState internalState)
|
|
42
|
+
{
|
|
43
|
+
_x = internalState.State1;
|
|
44
|
+
_y = internalState.State2;
|
|
45
|
+
_cachedGaussian = internalState.Gaussian;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public override uint NextUint()
|
|
49
|
+
{
|
|
50
|
+
unchecked
|
|
51
|
+
{
|
|
52
|
+
ulong xp = _x;
|
|
53
|
+
_x = 15241094284759029579UL * _y;
|
|
54
|
+
_y = Rol64(_y, 27) + xp;
|
|
55
|
+
return (uint)xp;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public override IRandom Copy()
|
|
60
|
+
{
|
|
61
|
+
return new RomuDuo(InternalState);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
65
|
+
private static ulong Rol64(ulong x, int k)
|
|
66
|
+
{
|
|
67
|
+
return (x << k) | (x >> (64 - k));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public override bool Equals(object obj)
|
|
71
|
+
{
|
|
72
|
+
return Equals(obj as RomuDuo);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
public bool Equals(RomuDuo other)
|
|
76
|
+
{
|
|
77
|
+
if (other == null)
|
|
78
|
+
{
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return _x == other._x && _y == other._y;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
public override int GetHashCode()
|
|
86
|
+
{
|
|
87
|
+
return Objects.ValueTypeHashCode(_x, _y);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
public override string ToString()
|
|
91
|
+
{
|
|
92
|
+
return this.ToJson();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
public int CompareTo(object obj)
|
|
96
|
+
{
|
|
97
|
+
return CompareTo(obj as RomuDuo);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
public int CompareTo(RomuDuo other)
|
|
101
|
+
{
|
|
102
|
+
if (other == null)
|
|
103
|
+
{
|
|
104
|
+
return -1;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
int comparison = _x.CompareTo(other._x);
|
|
108
|
+
if (comparison != 0)
|
|
109
|
+
{
|
|
110
|
+
return comparison;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return _y.CompareTo(other._y);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
namespace UnityHelpers.Core.Random
|
|
2
|
+
{
|
|
3
|
+
using System;
|
|
4
|
+
using System.Runtime.Serialization;
|
|
5
|
+
using System.Text.Json.Serialization;
|
|
6
|
+
|
|
7
|
+
[Serializable]
|
|
8
|
+
[DataContract]
|
|
9
|
+
public sealed class SplitMix64
|
|
10
|
+
: AbstractRandom,
|
|
11
|
+
IEquatable<SplitMix64>,
|
|
12
|
+
IComparable,
|
|
13
|
+
IComparable<SplitMix64>
|
|
14
|
+
{
|
|
15
|
+
public static IRandom Instance => ThreadLocalRandom<SplitMix64>.Instance;
|
|
16
|
+
|
|
17
|
+
public override RandomState InternalState => new(_state, 0, _cachedGaussian);
|
|
18
|
+
|
|
19
|
+
internal ulong _state;
|
|
20
|
+
|
|
21
|
+
public SplitMix64()
|
|
22
|
+
: this(Guid.NewGuid()) { }
|
|
23
|
+
|
|
24
|
+
public SplitMix64(Guid guid)
|
|
25
|
+
: this(BitConverter.ToUInt64(guid.ToByteArray(), 0)) { }
|
|
26
|
+
|
|
27
|
+
public SplitMix64(ulong seed)
|
|
28
|
+
{
|
|
29
|
+
_state = seed;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
[JsonConstructor]
|
|
33
|
+
public SplitMix64(RandomState internalState)
|
|
34
|
+
{
|
|
35
|
+
_state = internalState.State1;
|
|
36
|
+
_cachedGaussian = internalState.Gaussian;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public override uint NextUint()
|
|
40
|
+
{
|
|
41
|
+
unchecked
|
|
42
|
+
{
|
|
43
|
+
_state += 0x9E3779B97F4A7C15UL;
|
|
44
|
+
|
|
45
|
+
ulong z = _state;
|
|
46
|
+
z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9UL;
|
|
47
|
+
z = (z ^ (z >> 27)) * 0x94D049BB133111EBUL;
|
|
48
|
+
z ^= (z >> 31);
|
|
49
|
+
|
|
50
|
+
return (uint)z;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public override IRandom Copy()
|
|
55
|
+
{
|
|
56
|
+
return new SplitMix64(InternalState);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public bool Equals(SplitMix64 other)
|
|
60
|
+
{
|
|
61
|
+
if (other == null)
|
|
62
|
+
{
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return _state == other._state;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public int CompareTo(object obj)
|
|
70
|
+
{
|
|
71
|
+
return CompareTo(obj as SplitMix64);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public int CompareTo(SplitMix64 other)
|
|
75
|
+
{
|
|
76
|
+
if (other == null)
|
|
77
|
+
{
|
|
78
|
+
return -1;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return _state.CompareTo(other._state);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
public override int GetHashCode()
|
|
85
|
+
{
|
|
86
|
+
return _state.GetHashCode();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
public override string ToString()
|
|
90
|
+
{
|
|
91
|
+
return $"{{\"State\": {_state}}}";
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
namespace UnityHelpers.Core.Random
|
|
2
|
+
{
|
|
3
|
+
using System;
|
|
4
|
+
using System.Runtime.CompilerServices;
|
|
5
|
+
using System.Text.Json.Serialization;
|
|
6
|
+
using Extension;
|
|
7
|
+
using Helper;
|
|
8
|
+
|
|
9
|
+
public sealed class XorShiroRandom
|
|
10
|
+
: AbstractRandom,
|
|
11
|
+
IEquatable<XorShiroRandom>,
|
|
12
|
+
IComparable,
|
|
13
|
+
IComparable<XorShiroRandom>
|
|
14
|
+
{
|
|
15
|
+
public override RandomState InternalState => new(_s0, _s1, _cachedGaussian);
|
|
16
|
+
|
|
17
|
+
internal ulong _s0;
|
|
18
|
+
internal ulong _s1;
|
|
19
|
+
|
|
20
|
+
public XorShiroRandom()
|
|
21
|
+
: this(Guid.NewGuid()) { }
|
|
22
|
+
|
|
23
|
+
public XorShiroRandom(Guid guid)
|
|
24
|
+
{
|
|
25
|
+
byte[] bytes = guid.ToByteArray();
|
|
26
|
+
_s0 = BitConverter.ToUInt64(bytes, 0);
|
|
27
|
+
_s1 = BitConverter.ToUInt64(bytes, 8);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public XorShiroRandom(ulong seed1, ulong seed2)
|
|
31
|
+
{
|
|
32
|
+
_s0 = seed1;
|
|
33
|
+
_s1 = seed2;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
[JsonConstructor]
|
|
37
|
+
public XorShiroRandom(RandomState internalState)
|
|
38
|
+
{
|
|
39
|
+
_s0 = internalState.State1;
|
|
40
|
+
_s1 = internalState.State2;
|
|
41
|
+
_cachedGaussian = internalState.Gaussian;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public override uint NextUint()
|
|
45
|
+
{
|
|
46
|
+
unchecked
|
|
47
|
+
{
|
|
48
|
+
ulong s0 = _s0;
|
|
49
|
+
ulong s1 = _s1;
|
|
50
|
+
ulong result = s0 + s1;
|
|
51
|
+
|
|
52
|
+
s1 ^= s0;
|
|
53
|
+
_s0 = Rotl(s0, 24) ^ s1 ^ (s1 << 16);
|
|
54
|
+
_s1 = Rotl(s1, 37);
|
|
55
|
+
|
|
56
|
+
return (uint)result;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public override IRandom Copy()
|
|
61
|
+
{
|
|
62
|
+
return new XorShiroRandom(InternalState);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
66
|
+
private static ulong Rotl(ulong x, int k)
|
|
67
|
+
{
|
|
68
|
+
return (x << k) | (x >> (64 - k));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public override bool Equals(object obj)
|
|
72
|
+
{
|
|
73
|
+
return Equals(obj as XorShiroRandom);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public bool Equals(XorShiroRandom other)
|
|
77
|
+
{
|
|
78
|
+
if (other == null)
|
|
79
|
+
{
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return _s0 == other._s0 && _s1 == other._s1;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
public override int GetHashCode()
|
|
87
|
+
{
|
|
88
|
+
return Objects.ValueTypeHashCode(_s0, _s1);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
public override string ToString()
|
|
92
|
+
{
|
|
93
|
+
return this.ToJson();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
public int CompareTo(object obj)
|
|
97
|
+
{
|
|
98
|
+
return CompareTo(obj as XorShiroRandom);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
public int CompareTo(XorShiroRandom other)
|
|
102
|
+
{
|
|
103
|
+
if (other == null)
|
|
104
|
+
{
|
|
105
|
+
return -1;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
int comparison = _s0.CompareTo(other._s0);
|
|
109
|
+
if (comparison != 0)
|
|
110
|
+
{
|
|
111
|
+
return comparison;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return _s1.CompareTo(other._s1);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
namespace UnityHelpers.UI
|
|
2
|
+
{
|
|
3
|
+
using System;
|
|
4
|
+
using System.Collections.Generic;
|
|
5
|
+
using System.Diagnostics;
|
|
6
|
+
using System.Linq;
|
|
7
|
+
using System.Threading;
|
|
8
|
+
using System.Threading.Tasks;
|
|
9
|
+
using Core.Extension;
|
|
10
|
+
using Core.Helper;
|
|
11
|
+
using UnityEditor;
|
|
12
|
+
using UnityEngine;
|
|
13
|
+
using UnityEngine.UIElements;
|
|
14
|
+
using Utils;
|
|
15
|
+
using Debug = UnityEngine.Debug;
|
|
16
|
+
|
|
17
|
+
public readonly struct AnimatedSpriteLayer
|
|
18
|
+
{
|
|
19
|
+
public const float FrameRate = 12f;
|
|
20
|
+
|
|
21
|
+
public readonly Vector2[] offsets;
|
|
22
|
+
public readonly Sprite[] frames;
|
|
23
|
+
public readonly float alpha;
|
|
24
|
+
|
|
25
|
+
public AnimatedSpriteLayer(
|
|
26
|
+
IEnumerable<Sprite> sprites,
|
|
27
|
+
IEnumerable<Vector2> offsets,
|
|
28
|
+
float alpha = 1
|
|
29
|
+
)
|
|
30
|
+
{
|
|
31
|
+
frames = sprites?.ToArray() ?? Array.Empty<Sprite>();
|
|
32
|
+
foreach (Sprite frame in frames)
|
|
33
|
+
{
|
|
34
|
+
if (frame == null)
|
|
35
|
+
{
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
frame.texture.MakeReadable();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
this.offsets =
|
|
43
|
+
offsets?.Zip(frames, (offset, frame) => frame.pixelsPerUnit * offset).ToArray()
|
|
44
|
+
?? Array.Empty<Vector2>();
|
|
45
|
+
Debug.Assert(
|
|
46
|
+
this.offsets.Length == frames.Length,
|
|
47
|
+
$"Expected {frames.Length} to match {this.offsets.Length}"
|
|
48
|
+
);
|
|
49
|
+
this.alpha = Mathf.Clamp01(alpha);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public AnimatedSpriteLayer(
|
|
53
|
+
AnimationClip clip,
|
|
54
|
+
IEnumerable<Vector2> offsets,
|
|
55
|
+
float alpha = 1
|
|
56
|
+
)
|
|
57
|
+
: this(
|
|
58
|
+
#if UNITY_EDITOR
|
|
59
|
+
clip.GetSpritesFromClip(),
|
|
60
|
+
#else
|
|
61
|
+
Enumerable.Empty<Sprite>(),
|
|
62
|
+
#endif
|
|
63
|
+
offsets, alpha) { }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
public sealed class LayeredImage : VisualElement
|
|
67
|
+
{
|
|
68
|
+
private readonly AnimatedSpriteLayer[] _layers;
|
|
69
|
+
private readonly Texture2D[] _computed;
|
|
70
|
+
private readonly Color _backgroundColor;
|
|
71
|
+
|
|
72
|
+
private readonly Rect? _largestArea;
|
|
73
|
+
|
|
74
|
+
public LayeredImage(
|
|
75
|
+
IEnumerable<AnimatedSpriteLayer> inputSpriteLayers,
|
|
76
|
+
Color? backgroundColor = null,
|
|
77
|
+
float fps = AnimatedSpriteLayer.FrameRate
|
|
78
|
+
)
|
|
79
|
+
{
|
|
80
|
+
_layers = inputSpriteLayers.ToArray();
|
|
81
|
+
_backgroundColor = backgroundColor ?? Color.white;
|
|
82
|
+
_computed = ComputeTextures().ToArray();
|
|
83
|
+
_largestArea = null;
|
|
84
|
+
foreach (Texture2D computed in _computed)
|
|
85
|
+
{
|
|
86
|
+
if (_largestArea == null)
|
|
87
|
+
{
|
|
88
|
+
_largestArea = new Rect(0, 0, computed.width, computed.height);
|
|
89
|
+
}
|
|
90
|
+
else
|
|
91
|
+
{
|
|
92
|
+
Rect largestArea = _largestArea.Value;
|
|
93
|
+
largestArea.width = Mathf.Max(largestArea.width, computed.width);
|
|
94
|
+
largestArea.height = Mathf.Max(largestArea.height, computed.height);
|
|
95
|
+
_largestArea = largestArea;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
Render(0);
|
|
100
|
+
float fpsMs = 1000f / fps;
|
|
101
|
+
if (1 < _computed.Length)
|
|
102
|
+
{
|
|
103
|
+
#if UNITY_EDITOR
|
|
104
|
+
if (!Application.isPlaying)
|
|
105
|
+
{
|
|
106
|
+
TimeSpan lastTick = TimeSpan.Zero;
|
|
107
|
+
TimeSpan fpsSpan = TimeSpan.FromMilliseconds(fpsMs);
|
|
108
|
+
int index = 0;
|
|
109
|
+
Stopwatch timer = Stopwatch.StartNew();
|
|
110
|
+
EditorApplication.update += () =>
|
|
111
|
+
{
|
|
112
|
+
TimeSpan elapsed = timer.Elapsed;
|
|
113
|
+
if (lastTick + fpsSpan < elapsed)
|
|
114
|
+
{
|
|
115
|
+
index = index.WrappedIncrement(_computed.Length);
|
|
116
|
+
lastTick = elapsed;
|
|
117
|
+
Render(index);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
#endif
|
|
124
|
+
{
|
|
125
|
+
int index = 0;
|
|
126
|
+
CoroutineHandler.Instance.StartFunctionAsCoroutine(
|
|
127
|
+
() =>
|
|
128
|
+
{
|
|
129
|
+
index = index.WrappedIncrement(_computed.Length);
|
|
130
|
+
Render(index);
|
|
131
|
+
},
|
|
132
|
+
1f / fps
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private void Render(int index)
|
|
139
|
+
{
|
|
140
|
+
Texture2D computed = _computed[index];
|
|
141
|
+
if (computed != null)
|
|
142
|
+
{
|
|
143
|
+
style.backgroundImage = computed;
|
|
144
|
+
style.width = computed.width;
|
|
145
|
+
style.height = computed.height;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
style.marginRight = 0;
|
|
149
|
+
style.marginBottom = 0;
|
|
150
|
+
if (_largestArea != null)
|
|
151
|
+
{
|
|
152
|
+
Rect largestArea = _largestArea.Value;
|
|
153
|
+
if (style.width.value.value < largestArea.width)
|
|
154
|
+
{
|
|
155
|
+
style.marginRight = largestArea.width - style.width.value.value;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (style.height.value.value < largestArea.height)
|
|
159
|
+
{
|
|
160
|
+
style.marginBottom = largestArea.height - style.height.value.value;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private IEnumerable<Texture2D> ComputeTextures()
|
|
166
|
+
{
|
|
167
|
+
const float pixelCutoff = 0.01f;
|
|
168
|
+
int frameCount = _layers.Select(layer => layer.frames.Length).Distinct().Single();
|
|
169
|
+
|
|
170
|
+
Color transparent = Color.clear;
|
|
171
|
+
for (int frameIndex = 0; frameIndex < frameCount; ++frameIndex)
|
|
172
|
+
{
|
|
173
|
+
int minX = int.MaxValue;
|
|
174
|
+
int maxX = int.MinValue;
|
|
175
|
+
int minY = int.MaxValue;
|
|
176
|
+
int maxY = int.MinValue;
|
|
177
|
+
foreach (AnimatedSpriteLayer layer in _layers)
|
|
178
|
+
{
|
|
179
|
+
if (!layer.frames.Any())
|
|
180
|
+
{
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
Sprite sprite = layer.frames[frameIndex];
|
|
185
|
+
Vector2 offset = layer.offsets[frameIndex];
|
|
186
|
+
Rect spriteRect = sprite.rect;
|
|
187
|
+
|
|
188
|
+
int left = Mathf.RoundToInt(offset.x + spriteRect.xMin);
|
|
189
|
+
int right = Mathf.RoundToInt(offset.x + spriteRect.xMax);
|
|
190
|
+
int bottom = Mathf.RoundToInt(offset.y + spriteRect.yMin);
|
|
191
|
+
int top = Mathf.RoundToInt(offset.y + spriteRect.yMax);
|
|
192
|
+
|
|
193
|
+
minX = Mathf.Min(minX, left);
|
|
194
|
+
maxX = Mathf.Max(maxX, right);
|
|
195
|
+
minY = Mathf.Min(minY, bottom);
|
|
196
|
+
maxY = Mathf.Max(maxY, top);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (minX == int.MaxValue)
|
|
200
|
+
{
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Calculate the width and height of the non-transparent region
|
|
205
|
+
int width = maxX - minX + 1;
|
|
206
|
+
int height = maxY - minY + 1;
|
|
207
|
+
|
|
208
|
+
Color[] pixels = new Color[width * height];
|
|
209
|
+
Array.Fill(pixels, Color.clear);
|
|
210
|
+
|
|
211
|
+
foreach (AnimatedSpriteLayer layer in _layers)
|
|
212
|
+
{
|
|
213
|
+
if (!layer.frames.Any())
|
|
214
|
+
{
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
Sprite sprite = layer.frames[frameIndex];
|
|
219
|
+
Vector2 offset = layer.offsets[frameIndex];
|
|
220
|
+
float alpha = layer.alpha;
|
|
221
|
+
int offsetX = Mathf.RoundToInt(offset.x);
|
|
222
|
+
int offsetY = Mathf.RoundToInt(offset.y);
|
|
223
|
+
Texture2D texture = sprite.texture;
|
|
224
|
+
Rect spriteRect = sprite.rect;
|
|
225
|
+
|
|
226
|
+
int spriteX = Mathf.RoundToInt(spriteRect.xMin);
|
|
227
|
+
int spriteWidth = Mathf.RoundToInt(spriteRect.width);
|
|
228
|
+
int spriteY = Mathf.RoundToInt(spriteRect.yMin);
|
|
229
|
+
int spriteHeight = Mathf.RoundToInt(spriteRect.height);
|
|
230
|
+
Color[] spritePixels = texture.GetPixels(
|
|
231
|
+
spriteX,
|
|
232
|
+
spriteY,
|
|
233
|
+
spriteWidth,
|
|
234
|
+
spriteHeight
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
Parallel.For(
|
|
238
|
+
0,
|
|
239
|
+
spritePixels.Length,
|
|
240
|
+
inIndex =>
|
|
241
|
+
{
|
|
242
|
+
int x = inIndex % spriteWidth;
|
|
243
|
+
int y = inIndex / spriteWidth;
|
|
244
|
+
|
|
245
|
+
Color pixelColor = spritePixels[inIndex];
|
|
246
|
+
if (pixelColor.a < pixelCutoff)
|
|
247
|
+
{
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
int textureX = offsetX + x + spriteX;
|
|
252
|
+
int textureY = offsetY + y + spriteY;
|
|
253
|
+
int index = textureY * width + textureX;
|
|
254
|
+
|
|
255
|
+
if (index < 0 || pixels.Length <= index)
|
|
256
|
+
{
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
Color existingColor = pixels[index];
|
|
261
|
+
if (existingColor == transparent)
|
|
262
|
+
{
|
|
263
|
+
existingColor = _backgroundColor;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
Color blendedColor = Color.Lerp(existingColor, pixelColor, alpha);
|
|
267
|
+
pixels[index] = blendedColor;
|
|
268
|
+
}
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Find the bounds of the non-transparent pixels in the temporary texture
|
|
273
|
+
int finalMinX = int.MaxValue;
|
|
274
|
+
int finalMaxX = int.MinValue;
|
|
275
|
+
int finalMinY = int.MaxValue;
|
|
276
|
+
int finalMaxY = int.MinValue;
|
|
277
|
+
|
|
278
|
+
Parallel.For(
|
|
279
|
+
0,
|
|
280
|
+
height * width,
|
|
281
|
+
inIndex =>
|
|
282
|
+
{
|
|
283
|
+
Color pixelColor = pixels[inIndex];
|
|
284
|
+
if (pixelColor.a < pixelCutoff)
|
|
285
|
+
{
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
int x = inIndex % width;
|
|
290
|
+
int y = inIndex / width;
|
|
291
|
+
|
|
292
|
+
int expectedX = finalMinX;
|
|
293
|
+
while (x < expectedX)
|
|
294
|
+
{
|
|
295
|
+
expectedX = Interlocked.CompareExchange(ref finalMinX, x, expectedX);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
expectedX = finalMaxX;
|
|
299
|
+
while (expectedX < x)
|
|
300
|
+
{
|
|
301
|
+
expectedX = Interlocked.CompareExchange(ref finalMaxX, x, expectedX);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
int expectedY = finalMinY;
|
|
305
|
+
while (y < expectedY)
|
|
306
|
+
{
|
|
307
|
+
expectedY = Interlocked.CompareExchange(ref finalMinY, y, expectedY);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
expectedY = finalMaxY;
|
|
311
|
+
while (expectedY < y)
|
|
312
|
+
{
|
|
313
|
+
expectedY = Interlocked.CompareExchange(ref finalMaxY, y, expectedY);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
if (finalMinX == int.MaxValue)
|
|
319
|
+
{
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Calculate the final width and height of the culled texture
|
|
324
|
+
int finalWidth = finalMaxX - finalMinX + 1;
|
|
325
|
+
int finalHeight = finalMaxY - finalMinY + 1;
|
|
326
|
+
|
|
327
|
+
Color[] finalPixels = new Color[finalWidth * finalHeight];
|
|
328
|
+
Array.Fill(finalPixels, _backgroundColor);
|
|
329
|
+
|
|
330
|
+
// Copy the non-transparent pixels from the temporary texture to the final texture
|
|
331
|
+
Parallel.For(
|
|
332
|
+
0,
|
|
333
|
+
finalWidth * finalHeight,
|
|
334
|
+
inIndex =>
|
|
335
|
+
{
|
|
336
|
+
int x = inIndex % finalWidth;
|
|
337
|
+
int y = inIndex / finalWidth;
|
|
338
|
+
int outerX = x + finalMinX;
|
|
339
|
+
int outerY = y + finalMinY;
|
|
340
|
+
Color pixelColor = pixels[outerY * width + outerX];
|
|
341
|
+
if (pixelColor.a < pixelCutoff)
|
|
342
|
+
{
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
finalPixels[y * finalWidth + x] = pixelColor;
|
|
346
|
+
}
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
Texture2D finalTexture = new(
|
|
350
|
+
finalWidth,
|
|
351
|
+
finalHeight,
|
|
352
|
+
TextureFormat.RGBA32,
|
|
353
|
+
mipChain: false,
|
|
354
|
+
linear: false,
|
|
355
|
+
createUninitialized: true
|
|
356
|
+
);
|
|
357
|
+
finalTexture.SetPixels(finalPixels);
|
|
358
|
+
finalTexture.Apply(false, false);
|
|
359
|
+
|
|
360
|
+
yield return finalTexture;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
package/Runtime/UI.meta
ADDED
|
@@ -25,6 +25,9 @@
|
|
|
25
25
|
RunTest(new XorShiftRandom(), timeout);
|
|
26
26
|
RunTest(new DotNetRandom(), timeout);
|
|
27
27
|
RunTest(new WyRandom(), timeout);
|
|
28
|
+
RunTest(new SplitMix64(), timeout);
|
|
29
|
+
RunTest(new RomuDuo(), timeout);
|
|
30
|
+
RunTest(new XorShiroRandom(), timeout);
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
private static void RunTest<T>(T random, TimeSpan timeout)
|