com.wallstop-studios.unity-helpers 2.0.0-rc76.1 → 2.0.0-rc76.4
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/Sprites/SpriteCropper.cs +9 -2
- package/Editor/Sprites/SpritePivotAdjustor.cs +259 -0
- package/Editor/Sprites/SpritePivotAdjustor.cs.meta +3 -0
- package/Runtime/Core/DataStructure/Circle.cs +1 -1
- package/Runtime/Core/DataStructure/CyclicBuffer.cs +65 -2
- package/Runtime/Core/DataStructure/ISpatialTree.cs +7 -46
- package/Runtime/Core/DataStructure/KDTree.cs +266 -130
- package/Runtime/Core/DataStructure/QuadTree.cs +258 -128
- package/Runtime/Core/DataStructure/Trie.cs +359 -0
- package/Runtime/Core/DataStructure/Trie.cs.meta +3 -0
- package/Runtime/Core/Extension/EnumExtensions.cs +4 -4
- package/Runtime/Core/Helper/UnityMainThreadDispatcher.cs +3 -1
- package/Runtime/Core/Threading/SingleThreadedThreadPool.cs +7 -2
- package/Runtime/UI/LayeredImage.cs +0 -1
- package/Tests/Runtime/DataStructures/SpatialTreeTests.cs +6 -6
- package/Tests/Runtime/Performance/QuadTreePerformanceTests.cs +1 -1
- package/Tests/Runtime/Performance/SpatialTreePerformanceTest.cs +23 -18
- package/package.json +4 -1
- package/Editor/UI.meta +0 -3
|
@@ -180,6 +180,7 @@
|
|
|
180
180
|
return;
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
+
HashSet<string> processedFiles = new(StringComparer.OrdinalIgnoreCase);
|
|
183
184
|
List<string> needReprocessing = new();
|
|
184
185
|
string lastProcessed = null;
|
|
185
186
|
try
|
|
@@ -214,6 +215,10 @@
|
|
|
214
215
|
for (int i = 0; i < _filesToProcess.Count; ++i)
|
|
215
216
|
{
|
|
216
217
|
string file = _filesToProcess[i];
|
|
218
|
+
if (!processedFiles.Add(file))
|
|
219
|
+
{
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
217
222
|
lastProcessed = file;
|
|
218
223
|
EditorUtility.DisplayProgressBar(
|
|
219
224
|
Name,
|
|
@@ -292,8 +297,10 @@
|
|
|
292
297
|
return;
|
|
293
298
|
}
|
|
294
299
|
|
|
295
|
-
|
|
296
|
-
|
|
300
|
+
if (
|
|
301
|
+
AssetImporter.GetAtPath(assetPath)
|
|
302
|
+
is not TextureImporter { textureType: TextureImporterType.Sprite } importer
|
|
303
|
+
)
|
|
297
304
|
{
|
|
298
305
|
return;
|
|
299
306
|
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
namespace WallstopStudios.UnityHelpers.Editor.Sprites
|
|
2
|
+
{
|
|
3
|
+
#if UNITY_EDITOR
|
|
4
|
+
using System;
|
|
5
|
+
using System.Collections.Generic;
|
|
6
|
+
using System.IO;
|
|
7
|
+
using System.Linq;
|
|
8
|
+
using System.Text.RegularExpressions;
|
|
9
|
+
using System.Threading;
|
|
10
|
+
using System.Threading.Tasks;
|
|
11
|
+
using Core.Extension;
|
|
12
|
+
using CustomEditors;
|
|
13
|
+
using UnityEditor;
|
|
14
|
+
using UnityEngine;
|
|
15
|
+
using Object = UnityEngine.Object;
|
|
16
|
+
|
|
17
|
+
public class SpritePivotAdjuster : EditorWindow
|
|
18
|
+
{
|
|
19
|
+
private const float AlphaCutoff = 0.01f;
|
|
20
|
+
|
|
21
|
+
private static readonly string[] ImageFileExtensions =
|
|
22
|
+
{
|
|
23
|
+
".png",
|
|
24
|
+
".jpg",
|
|
25
|
+
".jpeg",
|
|
26
|
+
".bmp",
|
|
27
|
+
".tga",
|
|
28
|
+
".psd",
|
|
29
|
+
".gif",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
[SerializeField]
|
|
33
|
+
private List<Object> _directoryPaths = new();
|
|
34
|
+
|
|
35
|
+
[SerializeField]
|
|
36
|
+
private string _spriteNameRegex = ".*";
|
|
37
|
+
|
|
38
|
+
private SerializedObject _serializedObject;
|
|
39
|
+
private SerializedProperty _directoryPathsProperty;
|
|
40
|
+
private SerializedProperty _spriteNameRegexProperty;
|
|
41
|
+
|
|
42
|
+
private List<string> _filesToProcess;
|
|
43
|
+
private Regex _regex;
|
|
44
|
+
|
|
45
|
+
[MenuItem("Tools/Wallstop Studios/Unity Helpers/Sprite Pivot Adjuster")]
|
|
46
|
+
public static void ShowWindow()
|
|
47
|
+
{
|
|
48
|
+
GetWindow<SpritePivotAdjuster>("Sprite Pivot Adjuster");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private void OnEnable()
|
|
52
|
+
{
|
|
53
|
+
_serializedObject = new SerializedObject(this);
|
|
54
|
+
_directoryPathsProperty = _serializedObject.FindProperty(nameof(_directoryPaths));
|
|
55
|
+
_spriteNameRegexProperty = _serializedObject.FindProperty(nameof(_spriteNameRegex));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private void OnGUI()
|
|
59
|
+
{
|
|
60
|
+
EditorGUILayout.LabelField("Input directories", EditorStyles.boldLabel);
|
|
61
|
+
_serializedObject.Update();
|
|
62
|
+
PersistentDirectoryGUI.PathSelectorObjectArray(
|
|
63
|
+
_directoryPathsProperty,
|
|
64
|
+
nameof(SpriteCropper)
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
_serializedObject.ApplyModifiedProperties();
|
|
68
|
+
if (GUILayout.Button("Find Sprites To Process"))
|
|
69
|
+
{
|
|
70
|
+
_regex = !string.IsNullOrWhiteSpace(_spriteNameRegex)
|
|
71
|
+
? new Regex(_spriteNameRegex)
|
|
72
|
+
: null;
|
|
73
|
+
FindFilesToProcess();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (_filesToProcess is { Count: > 0 })
|
|
77
|
+
{
|
|
78
|
+
GUILayout.Label(
|
|
79
|
+
$"Found {_filesToProcess.Count} sprites to process.",
|
|
80
|
+
EditorStyles.boldLabel
|
|
81
|
+
);
|
|
82
|
+
if (GUILayout.Button("Adjust Pivots in Directory"))
|
|
83
|
+
{
|
|
84
|
+
AdjustPivotsInDirectory();
|
|
85
|
+
_filesToProcess = null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else if (_filesToProcess != null)
|
|
89
|
+
{
|
|
90
|
+
GUILayout.Label(
|
|
91
|
+
"No sprites found to process in the selected directories.",
|
|
92
|
+
EditorStyles.label
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private void FindFilesToProcess()
|
|
98
|
+
{
|
|
99
|
+
_filesToProcess = new List<string>();
|
|
100
|
+
if (_directoryPaths is not { Count: > 0 })
|
|
101
|
+
{
|
|
102
|
+
this.LogWarn($"No input directories selected.");
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
foreach (Object maybeDirectory in _directoryPaths.Where(d => d != null))
|
|
107
|
+
{
|
|
108
|
+
string assetPath = AssetDatabase.GetAssetPath(maybeDirectory);
|
|
109
|
+
if (!AssetDatabase.IsValidFolder(assetPath))
|
|
110
|
+
{
|
|
111
|
+
this.LogWarn($"Skipping invalid path: {assetPath}");
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
IEnumerable<string> files = Directory
|
|
116
|
+
.GetFiles(assetPath, "*.*", SearchOption.AllDirectories)
|
|
117
|
+
.Where(file =>
|
|
118
|
+
Array.Exists(
|
|
119
|
+
ImageFileExtensions,
|
|
120
|
+
extension =>
|
|
121
|
+
file.EndsWith(extension, StringComparison.OrdinalIgnoreCase)
|
|
122
|
+
)
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
foreach (string file in files)
|
|
126
|
+
{
|
|
127
|
+
string fileName = Path.GetFileNameWithoutExtension(file);
|
|
128
|
+
if (_regex != null && !_regex.IsMatch(fileName))
|
|
129
|
+
{
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
_filesToProcess.Add(file);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
Repaint();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private void AdjustPivotsInDirectory()
|
|
140
|
+
{
|
|
141
|
+
HashSet<string> processedFiles = new(StringComparer.OrdinalIgnoreCase);
|
|
142
|
+
List<TextureImporter> importers = new();
|
|
143
|
+
AssetDatabase.StartAssetEditing();
|
|
144
|
+
try
|
|
145
|
+
{
|
|
146
|
+
for (int i = 0; i < _filesToProcess.Count; i++)
|
|
147
|
+
{
|
|
148
|
+
string assetPath = _filesToProcess[i];
|
|
149
|
+
if (!processedFiles.Add(assetPath))
|
|
150
|
+
{
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (
|
|
154
|
+
AssetImporter.GetAtPath(assetPath)
|
|
155
|
+
is not TextureImporter { textureType: TextureImporterType.Sprite } importer
|
|
156
|
+
)
|
|
157
|
+
{
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
Sprite sprite = AssetDatabase.LoadAssetAtPath<Sprite>(assetPath);
|
|
162
|
+
|
|
163
|
+
if (sprite == null)
|
|
164
|
+
{
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
EditorUtility.DisplayProgressBar(
|
|
169
|
+
"Processing sprites",
|
|
170
|
+
$"Processing {sprite.name}",
|
|
171
|
+
(float)i / _filesToProcess.Count
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
if (importer.spriteImportMode == SpriteImportMode.Single)
|
|
175
|
+
{
|
|
176
|
+
TextureImporterSettings settings = new();
|
|
177
|
+
importer.ReadTextureSettings(settings);
|
|
178
|
+
|
|
179
|
+
Vector2 newPivot = CalculateCenterOfMassPivot(sprite);
|
|
180
|
+
settings.spritePivot = newPivot;
|
|
181
|
+
settings.spriteAlignment = (int)SpriteAlignment.Custom;
|
|
182
|
+
importer.SetTextureSettings(settings);
|
|
183
|
+
|
|
184
|
+
importer.spritePivot = newPivot;
|
|
185
|
+
importer.SaveAndReimport();
|
|
186
|
+
importers.Add(importer);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
finally
|
|
191
|
+
{
|
|
192
|
+
EditorUtility.ClearProgressBar();
|
|
193
|
+
AssetDatabase.StopAssetEditing();
|
|
194
|
+
foreach (TextureImporter importer in importers)
|
|
195
|
+
{
|
|
196
|
+
importer.SaveAndReimport();
|
|
197
|
+
}
|
|
198
|
+
AssetDatabase.SaveAssets();
|
|
199
|
+
AssetDatabase.Refresh();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private static Vector2 CalculateCenterOfMassPivot(Sprite sprite)
|
|
204
|
+
{
|
|
205
|
+
Texture2D texture = sprite.texture;
|
|
206
|
+
Rect spriteRect = sprite.rect;
|
|
207
|
+
int startX = Mathf.FloorToInt(spriteRect.x);
|
|
208
|
+
int startY = Mathf.FloorToInt(spriteRect.y);
|
|
209
|
+
int width = Mathf.FloorToInt(spriteRect.width);
|
|
210
|
+
int height = Mathf.FloorToInt(spriteRect.height);
|
|
211
|
+
|
|
212
|
+
long totalX = 0;
|
|
213
|
+
long totalY = 0;
|
|
214
|
+
long pixelCount = 0;
|
|
215
|
+
|
|
216
|
+
Color[] pixels = texture.GetPixels(startX, startY, width, height);
|
|
217
|
+
|
|
218
|
+
Parallel.For(
|
|
219
|
+
0,
|
|
220
|
+
height,
|
|
221
|
+
() => (sumX: 0L, sumY: 0L, count: 0L),
|
|
222
|
+
(y, _, local) =>
|
|
223
|
+
{
|
|
224
|
+
int rowOffset = y * width;
|
|
225
|
+
for (int x = 0; x < width; ++x)
|
|
226
|
+
{
|
|
227
|
+
if (pixels[rowOffset + x].a > AlphaCutoff)
|
|
228
|
+
{
|
|
229
|
+
local.sumX += x;
|
|
230
|
+
local.sumY += y;
|
|
231
|
+
local.count++;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return local;
|
|
235
|
+
},
|
|
236
|
+
local =>
|
|
237
|
+
{
|
|
238
|
+
Interlocked.Add(ref totalX, local.sumX);
|
|
239
|
+
Interlocked.Add(ref totalY, local.sumY);
|
|
240
|
+
Interlocked.Add(ref pixelCount, local.count);
|
|
241
|
+
}
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
if (pixelCount == 0L)
|
|
245
|
+
{
|
|
246
|
+
return new Vector2(0.5f, 0.5f);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
double averageX = (double)totalX / pixelCount;
|
|
250
|
+
double averageY = (double)totalY / pixelCount;
|
|
251
|
+
|
|
252
|
+
double pivotX = averageX / width;
|
|
253
|
+
double pivotY = averageY / height;
|
|
254
|
+
|
|
255
|
+
return new Vector2((float)pivotX, (float)pivotY);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
#endif
|
|
259
|
+
}
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
public int Count { get; private set; }
|
|
54
54
|
|
|
55
55
|
private readonly List<T> _buffer;
|
|
56
|
+
private readonly List<T> _cache;
|
|
56
57
|
private int _position;
|
|
57
58
|
|
|
58
59
|
public T this[int index]
|
|
@@ -80,9 +81,13 @@
|
|
|
80
81
|
_position = 0;
|
|
81
82
|
Count = 0;
|
|
82
83
|
_buffer = new List<T>();
|
|
83
|
-
|
|
84
|
+
_cache = new List<T>();
|
|
85
|
+
if (initialContents != null)
|
|
84
86
|
{
|
|
85
|
-
|
|
87
|
+
foreach (T item in initialContents)
|
|
88
|
+
{
|
|
89
|
+
Add(item);
|
|
90
|
+
}
|
|
86
91
|
}
|
|
87
92
|
}
|
|
88
93
|
|
|
@@ -124,6 +129,64 @@
|
|
|
124
129
|
}
|
|
125
130
|
}
|
|
126
131
|
|
|
132
|
+
public bool Remove(T element, IEqualityComparer<T> comparer = null)
|
|
133
|
+
{
|
|
134
|
+
bool removed = false;
|
|
135
|
+
_cache.Clear();
|
|
136
|
+
comparer ??= EqualityComparer<T>.Default;
|
|
137
|
+
foreach (T item in this)
|
|
138
|
+
{
|
|
139
|
+
if (!removed && comparer.Equals(item, element))
|
|
140
|
+
{
|
|
141
|
+
removed = true;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
_cache.Add(item);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (!removed)
|
|
148
|
+
{
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
Clear();
|
|
153
|
+
foreach (T item in _cache)
|
|
154
|
+
{
|
|
155
|
+
Add(item);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
public int RemoveAll(Func<T, bool> predicate)
|
|
162
|
+
{
|
|
163
|
+
int removedCount = 0;
|
|
164
|
+
foreach (T item in this)
|
|
165
|
+
{
|
|
166
|
+
if (predicate(item))
|
|
167
|
+
{
|
|
168
|
+
removedCount++;
|
|
169
|
+
}
|
|
170
|
+
else
|
|
171
|
+
{
|
|
172
|
+
_cache.Add(item);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (removedCount == 0)
|
|
177
|
+
{
|
|
178
|
+
return 0;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
Clear();
|
|
182
|
+
foreach (T item in _cache)
|
|
183
|
+
{
|
|
184
|
+
Add(item);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return removedCount;
|
|
188
|
+
}
|
|
189
|
+
|
|
127
190
|
public void Clear()
|
|
128
191
|
{
|
|
129
192
|
Count = 0;
|
|
@@ -1,59 +1,20 @@
|
|
|
1
1
|
namespace WallstopStudios.UnityHelpers.Core.DataStructure
|
|
2
2
|
{
|
|
3
|
-
using System;
|
|
4
3
|
using System.Collections.Generic;
|
|
5
|
-
using System.Linq;
|
|
6
4
|
using UnityEngine;
|
|
7
5
|
|
|
8
6
|
public interface ISpatialTree<T>
|
|
9
7
|
{
|
|
10
8
|
Bounds Boundary { get; }
|
|
11
|
-
Func<T, Vector2> ElementTransformer { get; }
|
|
12
9
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
Circle minimumArea = new(position, minimumRange);
|
|
20
|
-
return GetElementsInBounds(
|
|
21
|
-
new Bounds(
|
|
22
|
-
new Vector3(position.x, position.y, 0f),
|
|
23
|
-
new Vector3(range * 2f, range * 2f, 1f)
|
|
24
|
-
)
|
|
25
|
-
)
|
|
26
|
-
.Where(element =>
|
|
27
|
-
{
|
|
28
|
-
Vector2 elementPosition = elementTransformer(element);
|
|
29
|
-
if (!area.Contains(elementPosition))
|
|
30
|
-
{
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
10
|
+
List<T> GetElementsInRange(
|
|
11
|
+
Vector2 position,
|
|
12
|
+
float range,
|
|
13
|
+
List<T> elementsInRange,
|
|
14
|
+
float minimumRange = 0f
|
|
15
|
+
);
|
|
33
16
|
|
|
34
|
-
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return GetElementsInBounds(
|
|
39
|
-
new Bounds(
|
|
40
|
-
new Vector3(position.x, position.y, 0f),
|
|
41
|
-
new Vector3(range * 2f, range * 2f, 1f)
|
|
42
|
-
)
|
|
43
|
-
)
|
|
44
|
-
.Where(element =>
|
|
45
|
-
{
|
|
46
|
-
Vector2 elementPosition = elementTransformer(element);
|
|
47
|
-
if (!area.Contains(elementPosition))
|
|
48
|
-
{
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return true;
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
IEnumerable<T> GetElementsInBounds(Bounds bounds);
|
|
17
|
+
List<T> GetElementsInBounds(Bounds bounds, List<T> elementsInBounds);
|
|
57
18
|
|
|
58
19
|
void GetApproximateNearestNeighbors(Vector2 position, int count, List<T> nearestNeighbors);
|
|
59
20
|
}
|