com.wallstop-studios.unity-helpers 2.0.0-rc73.7 → 2.0.0-rc73.8

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.
@@ -34,7 +34,7 @@
34
34
  float yN = Math.Max(center.y, rectangle.y);
35
35
  float dX = xN - center.x;
36
36
  float dY = yN - center.y;
37
- return (dX * dX + dY * dY) <= _radiusSquared;
37
+ return dX * dX + dY * dY <= _radiusSquared;
38
38
  }
39
39
 
40
40
  public bool Overlaps(Bounds bounds)
@@ -45,7 +45,7 @@
45
45
 
46
46
  Bounds[] quadrants =
47
47
  {
48
- new Bounds(
48
+ new(
49
49
  new Vector3(
50
50
  boundary.center.x - halfQuadrantSize.x,
51
51
  boundary.center.y + halfQuadrantSize.y,
@@ -53,7 +53,7 @@
53
53
  ),
54
54
  quadrantSize
55
55
  ),
56
- new Bounds(
56
+ new(
57
57
  new Vector3(
58
58
  boundary.center.x + halfQuadrantSize.x,
59
59
  boundary.center.y + halfQuadrantSize.y,
@@ -61,7 +61,7 @@
61
61
  ),
62
62
  quadrantSize
63
63
  ),
64
- new Bounds(
64
+ new(
65
65
  new Vector3(
66
66
  boundary.center.x + halfQuadrantSize.x,
67
67
  boundary.center.y - halfQuadrantSize.y,
@@ -69,7 +69,7 @@
69
69
  ),
70
70
  quadrantSize
71
71
  ),
72
- new Bounds(
72
+ new(
73
73
  new Vector3(
74
74
  boundary.center.x - halfQuadrantSize.x,
75
75
  boundary.center.y - halfQuadrantSize.y,
@@ -534,14 +534,14 @@
534
534
  rgb.b > 0.04045 ? Mathf.Pow((rgb.b + 0.055f) / 1.055f, 2.4f) : rgb.b / 12.92f;
535
535
 
536
536
  double x = (r * 0.4124 + g * 0.3576 + b * 0.1805) / 0.95047;
537
- double y = (r * 0.2126 + g * 0.7152 + b * 0.0722);
537
+ double y = r * 0.2126 + g * 0.7152 + b * 0.0722;
538
538
  double z = (r * 0.0193 + g * 0.1192 + b * 0.9505) / 1.08883;
539
539
 
540
- x = x > 0.008856 ? Mathf.Pow((float)x, 1f / 3f) : (7.787 * x) + 16f / 116f;
541
- y = y > 0.008856 ? Mathf.Pow((float)y, 1f / 3f) : (7.787 * y) + 16f / 116f;
542
- z = z > 0.008856 ? Mathf.Pow((float)z, 1f / 3f) : (7.787 * z) + 16f / 116f;
540
+ x = x > 0.008856 ? Mathf.Pow((float)x, 1f / 3f) : 7.787 * x + 16f / 116f;
541
+ y = y > 0.008856 ? Mathf.Pow((float)y, 1f / 3f) : 7.787 * y + 16f / 116f;
542
+ z = z > 0.008856 ? Mathf.Pow((float)z, 1f / 3f) : 7.787 * z + 16f / 116f;
543
543
 
544
- return new LABColor((116 * y) - 16, 500 * (x - y), 200 * (y - z));
544
+ return new LABColor(116 * y - 16, 500 * (x - y), 200 * (y - z));
545
545
  }
546
546
 
547
547
  private static Color LABToRGB(double l, double a, double b)
@@ -78,7 +78,7 @@
78
78
 
79
79
  public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> items, int size)
80
80
  {
81
- using var enumerator = items.GetEnumerator();
81
+ using IEnumerator<T> enumerator = items.GetEnumerator();
82
82
  bool hasNext = enumerator.MoveNext();
83
83
 
84
84
  IEnumerable<T> NextPartitionOf()
@@ -126,9 +126,9 @@
126
126
  xMin,
127
127
  yMin,
128
128
  zMin,
129
- (xMax - xMin) + 1,
130
- (yMax - yMin) + 1,
131
- (zMax - zMin) + 1
129
+ xMax - xMin + 1,
130
+ yMax - yMin + 1,
131
+ zMax - zMin + 1
132
132
  );
133
133
  }
134
134
 
@@ -160,9 +160,9 @@
160
160
  xMin,
161
161
  yMin,
162
162
  zMin,
163
- (xMax - xMin) + 1,
164
- (yMax - yMin) + 1,
165
- (zMax - zMin) + 1
163
+ xMax - xMin + 1,
164
+ yMax - yMin + 1,
165
+ zMax - zMin + 1
166
166
  );
167
167
  }
168
168
 
@@ -584,8 +584,8 @@
584
584
  public float LargestAngle(FastVector3Int point)
585
585
  {
586
586
  Vector2 worldPoint = _grid.CellToWorld(point);
587
- float angleFrom = Vector2.Angle((toWorld - fromWorld), (worldPoint - fromWorld));
588
- float angleTo = Vector2.Angle((fromWorld - toWorld), (worldPoint - toWorld));
587
+ float angleFrom = Vector2.Angle(toWorld - fromWorld, worldPoint - fromWorld);
588
+ float angleTo = Vector2.Angle(fromWorld - toWorld, worldPoint - toWorld);
589
589
  return Math.Max(angleFrom, angleTo);
590
590
  }
591
591
  }
@@ -1126,7 +1126,7 @@
1126
1126
  }
1127
1127
 
1128
1128
  if (
1129
- (newVector.x < position.x) == (position.x <= oldVector.x)
1129
+ newVector.x < position.x == position.x <= oldVector.x
1130
1130
  && (position.y - (long)lhs.y) * (rhs.x - lhs.x)
1131
1131
  < (rhs.y - (long)lhs.y) * (position.x - lhs.x)
1132
1132
  )
@@ -1274,7 +1274,7 @@
1274
1274
  yield break;
1275
1275
  }
1276
1276
 
1277
- scaleFactor *= (4 / 3f);
1277
+ scaleFactor *= 4 / 3f;
1278
1278
  }
1279
1279
  }
1280
1280
 
@@ -1416,8 +1416,8 @@
1416
1416
  float ty = v.y;
1417
1417
 
1418
1418
  Vector2 rotatedVector;
1419
- rotatedVector.x = (cos * tx) - (sin * ty);
1420
- rotatedVector.y = (sin * tx) + (cos * ty);
1419
+ rotatedVector.x = cos * tx - sin * ty;
1420
+ rotatedVector.y = sin * tx + cos * ty;
1421
1421
 
1422
1422
  return rotatedVector;
1423
1423
  }
@@ -1564,12 +1564,12 @@
1564
1564
 
1565
1565
  public static bool IsOnEdge2D(this FastVector3Int position, BoundsInt bounds)
1566
1566
  {
1567
- if (bounds.xMin == position.x || (bounds.xMax - 1) == position.x)
1567
+ if (bounds.xMin == position.x || bounds.xMax - 1 == position.x)
1568
1568
  {
1569
1569
  return bounds.yMin <= position.y && position.y < bounds.yMax;
1570
1570
  }
1571
1571
 
1572
- if (bounds.yMin == position.y || (bounds.yMax - 1) == position.y)
1572
+ if (bounds.yMin == position.y || bounds.yMax - 1 == position.y)
1573
1573
  {
1574
1574
  return bounds.xMin <= position.x && position.x < bounds.xMax;
1575
1575
  }
@@ -47,9 +47,9 @@
47
47
  }
48
48
 
49
49
  float a =
50
- (targetVelocity.x * targetVelocity.x)
51
- + (targetVelocity.y * targetVelocity.y)
52
- - (projectileSpeed * projectileSpeed);
50
+ targetVelocity.x * targetVelocity.x
51
+ + targetVelocity.y * targetVelocity.y
52
+ - projectileSpeed * projectileSpeed;
53
53
 
54
54
  float b =
55
55
  2
@@ -59,10 +59,10 @@
59
59
  );
60
60
 
61
61
  float c =
62
- ((target.x - launchLocation.x) * (target.x - launchLocation.x))
63
- + ((target.y - launchLocation.y) * (target.y - launchLocation.y));
62
+ (target.x - launchLocation.x) * (target.x - launchLocation.x)
63
+ + (target.y - launchLocation.y) * (target.y - launchLocation.y);
64
64
 
65
- float disc = b * b - (4 * a * c);
65
+ float disc = b * b - 4 * a * c;
66
66
  if (disc < 0)
67
67
  {
68
68
  return target;
@@ -72,8 +72,8 @@
72
72
  float t2 = (-1 * b - Mathf.Sqrt(disc)) / (2 * a);
73
73
  float t = Mathf.Max(t1, t2); // let us take the larger time value
74
74
 
75
- float aimX = target.x + (targetVelocity.x * t);
76
- float aimY = target.y + (targetVelocity.y * t);
75
+ float aimX = target.x + targetVelocity.x * t;
76
+ float aimY = target.y + targetVelocity.y * t;
77
77
 
78
78
  if (float.IsNaN(aimX) || float.IsNaN(aimY))
79
79
  {
@@ -283,7 +283,7 @@
283
283
  // optional delay execution from happening on 0, 1, 2, ... n-1 to 1, 2, ... n
284
284
  if (
285
285
  totalExecuted < totalCount
286
- && ((totalExecuted + (delay ? 1f : 0f)) / totalCount) <= percent
286
+ && (totalExecuted + (delay ? 1f : 0f)) / totalCount <= percent
287
287
  )
288
288
  {
289
289
  action();
@@ -137,11 +137,9 @@
137
137
  const string sizeCheck = "size=";
138
138
  AddDecoration(
139
139
  format =>
140
- (
141
- format.StartsWith(sizeCheck, StringComparison.OrdinalIgnoreCase)
142
- && int.TryParse(format.Substring(sizeCheck.Length), out _)
143
- || int.TryParse(format, out _)
144
- ),
140
+ format.StartsWith(sizeCheck, StringComparison.OrdinalIgnoreCase)
141
+ && int.TryParse(format.Substring(sizeCheck.Length), out _)
142
+ || int.TryParse(format, out _),
145
143
  format: (format, value) =>
146
144
  {
147
145
  if (!int.TryParse(format, out int size))
@@ -373,11 +371,26 @@
373
371
  )
374
372
  {
375
373
  bool stopLooping = false;
376
- foreach (var entry in _matchingDecorations)
374
+ foreach (
375
+ KeyValuePair<
376
+ int,
377
+ List<(
378
+ string tag,
379
+ bool editorOnly,
380
+ Func<string, bool> predicate,
381
+ Func<string, object, string> formatter
382
+ )>
383
+ > entry in _matchingDecorations
384
+ )
377
385
  {
378
386
  for (int i = 0; i < entry.Value.Count; i++)
379
387
  {
380
- var existingDecoration = entry.Value[i];
388
+ (
389
+ string tag,
390
+ bool editorOnly,
391
+ Func<string, bool> predicate,
392
+ Func<string, object, string> formatter
393
+ ) existingDecoration = entry.Value[i];
381
394
  if (
382
395
  !string.Equals(
383
396
  existingDecoration.tag,
@@ -459,7 +472,17 @@
459
472
  ) decoration
460
473
  )
461
474
  {
462
- foreach (var entry in _matchingDecorations)
475
+ foreach (
476
+ KeyValuePair<
477
+ int,
478
+ List<(
479
+ string tag,
480
+ bool editorOnly,
481
+ Func<string, bool> predicate,
482
+ Func<string, object, string> formatter
483
+ )>
484
+ > entry in _matchingDecorations
485
+ )
463
486
  {
464
487
  for (int i = 0; i < entry.Value.Count; ++i)
465
488
  {
@@ -156,7 +156,7 @@
156
156
  }
157
157
  }
158
158
 
159
- Transform transform = (component as Transform) ?? component.transform;
159
+ Transform transform = component as Transform ?? component.transform;
160
160
  if (transform == null)
161
161
  {
162
162
  return;
@@ -187,7 +187,7 @@
187
187
  behavior.enabled = enabled;
188
188
  }
189
189
 
190
- Transform transform = (component as Transform) ?? component.transform;
190
+ Transform transform = component as Transform ?? component.transform;
191
191
  if (transform == null)
192
192
  {
193
193
  return;
@@ -11,7 +11,7 @@
11
11
  public static DotNetRandom Instance => ThreadLocalRandom<DotNetRandom>.Instance;
12
12
 
13
13
  public override RandomState InternalState =>
14
- new RandomState(unchecked((ulong)_seed), state2: _numberGenerated);
14
+ new(unchecked((ulong)_seed), state2: _numberGenerated);
15
15
 
16
16
  private ulong _numberGenerated;
17
17
  private int _seed;
@@ -45,7 +45,7 @@
45
45
  ulong z = _state;
46
46
  z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9UL;
47
47
  z = (z ^ (z >> 27)) * 0x94D049BB133111EBUL;
48
- z ^= (z >> 31);
48
+ z ^= z >> 31;
49
49
 
50
50
  return (uint)z;
51
51
  }
@@ -54,11 +54,11 @@
54
54
  private static uint NextUintInternal(ref uint seed)
55
55
  {
56
56
  seed *= BitNoise1;
57
- seed ^= (seed >> 8);
57
+ seed ^= seed >> 8;
58
58
  seed += BitNoise2;
59
- seed ^= (seed << 8);
59
+ seed ^= seed << 8;
60
60
  seed *= BitNoise3;
61
- seed ^= (seed >> 8);
61
+ seed ^= seed >> 8;
62
62
  return seed;
63
63
  }
64
64
 
@@ -68,17 +68,17 @@
68
68
  uint result = unchecked((uint)x);
69
69
  result *= BitNoise1;
70
70
  result += seed;
71
- result ^= (result >> 8);
71
+ result ^= result >> 8;
72
72
  result += BitNoise2;
73
- result ^= (result << 8);
73
+ result ^= result << 8;
74
74
  result *= BitNoise3;
75
- result ^= (result >> 8);
75
+ result ^= result >> 8;
76
76
  return (result >> 8) * MagicFloat;
77
77
  }
78
78
 
79
79
  private static float NextNoise(int x, int y, uint seed)
80
80
  {
81
- return NextNoise(x + (LargePrime * y), seed);
81
+ return NextNoise(x + LargePrime * y, seed);
82
82
  }
83
83
  }
84
84
  }
@@ -5,7 +5,7 @@
5
5
  public static class ThreadLocalRandom<T>
6
6
  where T : IRandom, new()
7
7
  {
8
- private static readonly ThreadLocal<T> RandomCache = new ThreadLocal<T>(() => new T());
8
+ private static readonly ThreadLocal<T> RandomCache = new(() => new T());
9
9
 
10
10
  public static T Instance => RandomCache.Value;
11
11
  }
@@ -16,7 +16,7 @@
16
16
 
17
17
  public static WyRandom Instance => ThreadLocalRandom<WyRandom>.Instance;
18
18
 
19
- public override RandomState InternalState => new RandomState(_state);
19
+ public override RandomState InternalState => new(_state);
20
20
 
21
21
  private ulong _state;
22
22
 
@@ -297,7 +297,7 @@
297
297
  if (cosmeticEffectData.RequiresInstancing)
298
298
  {
299
299
  this.LogWarn(
300
- $"Double-deregistration detected for handle {handle:json}. Existing handles: [{(string.Join(",", _instancedCosmeticEffects.Keys))}]."
300
+ $"Double-deregistration detected for handle {handle:json}. Existing handles: [{string.Join(",", _instancedCosmeticEffects.Keys)}]."
301
301
  );
302
302
  continue;
303
303
  }
@@ -18,13 +18,13 @@
18
18
  {
19
19
  public const float FrameRate = 12f;
20
20
 
21
- public readonly Vector2[] offsets;
21
+ public readonly Vector2[] perFramePixelOffsets;
22
22
  public readonly Sprite[] frames;
23
23
  public readonly float alpha;
24
24
 
25
25
  public AnimatedSpriteLayer(
26
26
  IEnumerable<Sprite> sprites,
27
- IEnumerable<Vector2> offsets,
27
+ IEnumerable<Vector2>? worldSpaceOffsets = null,
28
28
  float alpha = 1
29
29
  )
30
30
  {
@@ -37,21 +37,45 @@
37
37
  }
38
38
 
39
39
  frame.texture.MakeReadable();
40
+ try
41
+ {
42
+ frame.texture.GetPixel(0, 0);
43
+ }
44
+ catch (UnityException e)
45
+ {
46
+ Debug.LogError(
47
+ $"Texture '{frame.texture.name}' for sprite '{frame.name}' is not readable. Please enable Read/Write in its import settings. Error: {e.Message}"
48
+ );
49
+ }
50
+ }
51
+
52
+ if (worldSpaceOffsets != null && frames is { Length: > 0 })
53
+ {
54
+ perFramePixelOffsets = worldSpaceOffsets
55
+ .Zip(
56
+ frames,
57
+ (offset, frame) =>
58
+ frame != null && frame.pixelsPerUnit > 0
59
+ ? frame.pixelsPerUnit * offset
60
+ : Vector2.zero
61
+ )
62
+ .ToArray();
63
+ Debug.Assert(
64
+ perFramePixelOffsets.Length == frames.Length,
65
+ $"Expected {frames.Length} sprite frames to match {perFramePixelOffsets.Length} offsets after processing."
66
+ );
67
+ }
68
+ else
69
+ {
70
+ perFramePixelOffsets = null;
40
71
  }
41
72
 
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
73
  this.alpha = Mathf.Clamp01(alpha);
50
74
  }
51
75
 
52
76
  public AnimatedSpriteLayer(
53
77
  AnimationClip clip,
54
- IEnumerable<Vector2> offsets,
78
+ IEnumerable<Vector2>? worldSpaceOffsets = null,
55
79
  float alpha = 1
56
80
  )
57
81
  : this(
@@ -60,7 +84,7 @@
60
84
  #else
61
85
  Enumerable.Empty<Sprite>(),
62
86
  #endif
63
- offsets, alpha) { }
87
+ worldSpaceOffsets, alpha) { }
64
88
  }
65
89
 
66
90
  public sealed class LayeredImage : VisualElement
@@ -68,7 +92,6 @@
68
92
  private readonly AnimatedSpriteLayer[] _layers;
69
93
  private readonly Texture2D[] _computed;
70
94
  private readonly Color _backgroundColor;
71
-
72
95
  private readonly Rect? _largestArea;
73
96
 
74
97
  public LayeredImage(
@@ -81,52 +104,73 @@
81
104
  _backgroundColor = backgroundColor ?? Color.white;
82
105
  _computed = ComputeTextures().ToArray();
83
106
  _largestArea = null;
84
- foreach (Texture2D computed in _computed)
107
+
108
+ foreach (Texture2D? computedTexture in _computed)
85
109
  {
110
+ if (computedTexture == null)
111
+ {
112
+ continue;
113
+ }
114
+
86
115
  if (_largestArea == null)
87
116
  {
88
- _largestArea = new Rect(0, 0, computed.width, computed.height);
117
+ _largestArea = new Rect(0, 0, computedTexture.width, computedTexture.height);
89
118
  }
90
119
  else
91
120
  {
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;
121
+ Rect currentLargest = _largestArea.Value;
122
+ currentLargest.width = Mathf.Max(currentLargest.width, computedTexture.width);
123
+ currentLargest.height = Mathf.Max(
124
+ currentLargest.height,
125
+ computedTexture.height
126
+ );
127
+ _largestArea = currentLargest;
96
128
  }
97
129
  }
98
130
 
99
131
  Render(0);
100
- float fpsMs = 1000f / fps;
101
- if (1 < _computed.Length)
132
+
133
+ if (_computed.Length > 1 && fps > 0)
102
134
  {
103
135
  #if UNITY_EDITOR
104
136
  if (!Application.isPlaying)
105
137
  {
106
138
  TimeSpan lastTick = TimeSpan.Zero;
107
- TimeSpan fpsSpan = TimeSpan.FromMilliseconds(fpsMs);
139
+ TimeSpan fpsSpan = TimeSpan.FromMilliseconds(1000f / fps);
108
140
  int index = 0;
109
141
  Stopwatch timer = Stopwatch.StartNew();
110
142
  EditorApplication.update += () =>
111
143
  {
144
+ if (panel == null)
145
+ {
146
+ EditorApplication.update = null;
147
+ return;
148
+ }
112
149
  TimeSpan elapsed = timer.Elapsed;
113
- if (lastTick + fpsSpan < elapsed)
150
+ if (lastTick + fpsSpan >= elapsed)
114
151
  {
115
- index = index.WrappedIncrement(_computed.Length);
116
- lastTick = elapsed;
117
- Render(index);
152
+ return;
118
153
  }
154
+
155
+ index = (index + 1) % _computed.Length;
156
+ lastTick = elapsed;
157
+ Render(index);
119
158
  };
120
159
  return;
121
160
  }
122
-
123
161
  #endif
162
+ if (Application.isPlaying && CoroutineHandler.Instance != null)
124
163
  {
125
164
  int index = 0;
126
165
  CoroutineHandler.Instance.StartFunctionAsCoroutine(
127
166
  () =>
128
167
  {
129
- index = index.WrappedIncrement(_computed.Length);
168
+ if (panel == null)
169
+ {
170
+ return;
171
+ }
172
+
173
+ index = (index + 1) % _computed.Length;
130
174
  Render(index);
131
175
  },
132
176
  1f / fps
@@ -137,6 +181,11 @@
137
181
 
138
182
  private void Render(int index)
139
183
  {
184
+ if (index < 0 || index >= _computed.Length)
185
+ {
186
+ return;
187
+ }
188
+
140
189
  Texture2D computed = _computed[index];
141
190
  if (computed != null)
142
191
  {
@@ -144,205 +193,303 @@
144
193
  style.width = computed.width;
145
194
  style.height = computed.height;
146
195
  }
196
+ else
197
+ {
198
+ style.backgroundImage = null;
199
+ style.width = _largestArea?.width ?? 0;
200
+ style.height = _largestArea?.height ?? 0;
201
+ }
147
202
 
148
203
  style.marginRight = 0;
149
204
  style.marginBottom = 0;
150
- if (_largestArea != null)
205
+ if (_largestArea == null)
151
206
  {
152
- Rect largestArea = _largestArea.Value;
153
- if (style.width.value.value < largestArea.width)
154
- {
155
- style.marginRight = largestArea.width - style.width.value.value;
156
- }
207
+ return;
208
+ }
157
209
 
158
- if (style.height.value.value < largestArea.height)
159
- {
160
- style.marginBottom = largestArea.height - style.height.value.value;
161
- }
210
+ Rect largestAreaRect = _largestArea.Value;
211
+ float currentWidth = computed != null ? computed.width : _largestArea?.width ?? 0;
212
+ float currentHeight = computed != null ? computed.height : _largestArea?.height ?? 0;
213
+
214
+ if (currentWidth < largestAreaRect.width)
215
+ {
216
+ style.marginRight = largestAreaRect.width - currentWidth;
217
+ }
218
+ if (currentHeight < largestAreaRect.height)
219
+ {
220
+ style.marginBottom = largestAreaRect.height - currentHeight;
162
221
  }
163
222
  }
164
223
 
165
- private IEnumerable<Texture2D> ComputeTextures()
224
+ private IEnumerable<Texture2D?> ComputeTextures()
166
225
  {
167
226
  const float pixelCutoff = 0.01f;
168
- int frameCount = _layers.Select(layer => layer.frames.Length).Distinct().Single();
227
+ if (_layers is not { Length: > 0 })
228
+ {
229
+ yield break;
230
+ }
231
+
232
+ int frameCount = 0;
233
+ foreach (AnimatedSpriteLayer layer in _layers)
234
+ {
235
+ if (layer.frames != null)
236
+ {
237
+ frameCount = Mathf.Max(frameCount, layer.frames.Length);
238
+ }
239
+ }
240
+ if (frameCount == 0)
241
+ {
242
+ yield break;
243
+ }
169
244
 
170
- Color transparent = Color.clear;
171
245
  for (int frameIndex = 0; frameIndex < frameCount; ++frameIndex)
172
246
  {
173
- int minX = int.MaxValue;
174
- int maxX = int.MinValue;
175
- int minY = int.MaxValue;
176
- int maxY = int.MinValue;
247
+ float overallMinX = float.MaxValue;
248
+ float overallMaxX = float.MinValue;
249
+ float overallMinY = float.MaxValue;
250
+ float overallMaxY = float.MinValue;
251
+ bool hasVisibleSpriteThisFrame = false;
252
+
177
253
  foreach (AnimatedSpriteLayer layer in _layers)
178
254
  {
179
- if (layer.frames.Length <= 0)
255
+ if (layer.frames == null || frameIndex >= layer.frames.Length)
180
256
  {
181
257
  continue;
182
258
  }
183
259
 
184
260
  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);
261
+ if (sprite == null)
262
+ {
263
+ continue;
264
+ }
265
+
266
+ hasVisibleSpriteThisFrame = true;
267
+ Rect spriteGeomRect = sprite.rect;
268
+ Vector2 pivot = sprite.pivot;
269
+
270
+ Vector2 additionalPixelOffset = Vector2.zero;
271
+ if (
272
+ layer.perFramePixelOffsets != null
273
+ && frameIndex < layer.perFramePixelOffsets.Length
274
+ )
275
+ {
276
+ additionalPixelOffset = layer.perFramePixelOffsets[frameIndex];
277
+ }
278
+
279
+ float spriteWorldMinX = -pivot.x + additionalPixelOffset.x;
280
+ float spriteWorldMaxX =
281
+ spriteGeomRect.width - pivot.x + additionalPixelOffset.x;
282
+ float spriteWorldMinY = -pivot.y + additionalPixelOffset.y;
283
+ float spriteWorldMaxY =
284
+ spriteGeomRect.height - pivot.y + additionalPixelOffset.y;
285
+
286
+ overallMinX = Mathf.Min(overallMinX, spriteWorldMinX);
287
+ overallMaxX = Mathf.Max(overallMaxX, spriteWorldMaxX);
288
+ overallMinY = Mathf.Min(overallMinY, spriteWorldMinY);
289
+ overallMaxY = Mathf.Max(overallMaxY, spriteWorldMaxY);
290
+ }
291
+
292
+ if (!hasVisibleSpriteThisFrame)
293
+ {
294
+ yield return null;
295
+ continue;
197
296
  }
198
297
 
199
- if (minX == int.MaxValue)
298
+ int compositeBufferOriginX = Mathf.FloorToInt(overallMinX);
299
+ int compositeBufferOriginY = Mathf.FloorToInt(overallMinY);
300
+ int compositeBufferWidth = Mathf.CeilToInt(overallMaxX) - compositeBufferOriginX;
301
+ int compositeBufferHeight = Mathf.CeilToInt(overallMaxY) - compositeBufferOriginY;
302
+
303
+ if (compositeBufferWidth <= 0 || compositeBufferHeight <= 0)
200
304
  {
305
+ yield return null;
201
306
  continue;
202
307
  }
203
308
 
204
- // Calculate the width and height of the non-transparent region
205
- int width = maxX - minX + 1;
206
- int height = maxY - minY + 1;
309
+ Color[] bufferPixels = new Color[compositeBufferWidth * compositeBufferHeight];
207
310
 
208
- Color[] pixels = new Color[width * height];
209
- Array.Fill(pixels, Color.clear);
311
+ Array.Fill(bufferPixels, Color.clear);
210
312
 
211
313
  foreach (AnimatedSpriteLayer layer in _layers)
212
314
  {
213
- if (layer.frames.Length <= 0)
315
+ if (layer.frames == null || frameIndex >= layer.frames.Length)
214
316
  {
215
317
  continue;
216
318
  }
217
319
 
218
320
  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
- );
321
+ if (sprite == null)
322
+ {
323
+ continue;
324
+ }
236
325
 
237
- Parallel.For(
238
- 0,
239
- spritePixels.Length,
240
- inIndex =>
241
- {
242
- int x = inIndex % spriteWidth;
243
- int y = inIndex / spriteWidth;
326
+ float layerAlpha = layer.alpha;
327
+ Texture2D spriteTexture = sprite.texture;
328
+ Rect spriteGeomRect = sprite.rect;
329
+ Vector2 pivot = sprite.pivot;
244
330
 
245
- Color pixelColor = spritePixels[inIndex];
246
- if (pixelColor.a < pixelCutoff)
247
- {
248
- return;
249
- }
331
+ Vector2 additionalPixelOffset = Vector2.zero;
332
+ if (
333
+ layer.perFramePixelOffsets != null
334
+ && frameIndex < layer.perFramePixelOffsets.Length
335
+ )
336
+ {
337
+ additionalPixelOffset = layer.perFramePixelOffsets[frameIndex];
338
+ }
250
339
 
251
- int textureX = (-1 * minX) + offsetX + x + spriteX;
252
- int textureY = (-1 * minY) + offsetY + y + spriteY;
253
- int index = textureY * width + textureX;
340
+ int spriteRectX = Mathf.FloorToInt(spriteGeomRect.x);
341
+ int spriteRectY = Mathf.FloorToInt(spriteGeomRect.y);
342
+ int spriteRectWidth = Mathf.FloorToInt(spriteGeomRect.width);
343
+ int spriteRectHeight = Mathf.FloorToInt(spriteGeomRect.height);
254
344
 
255
- if (index < 0 || pixels.Length <= index)
256
- {
257
- return;
258
- }
345
+ if (spriteRectWidth <= 0 || spriteRectHeight <= 0)
346
+ {
347
+ continue;
348
+ }
259
349
 
260
- Color existingColor = pixels[index];
261
- if (existingColor == transparent)
350
+ Color[] spriteRawPixels = spriteTexture.GetPixels(
351
+ spriteRectX,
352
+ spriteRectY,
353
+ spriteRectWidth,
354
+ spriteRectHeight
355
+ );
356
+
357
+ Parallel.For(
358
+ 0,
359
+ spriteRectHeight,
360
+ sySprite =>
361
+ {
362
+ for (int sxSprite = 0; sxSprite < spriteRectWidth; ++sxSprite)
262
363
  {
263
- existingColor = _backgroundColor;
364
+ Color spritePixelColor = spriteRawPixels[
365
+ sySprite * spriteRectWidth + sxSprite
366
+ ];
367
+
368
+ if (spritePixelColor.a < pixelCutoff)
369
+ {
370
+ continue;
371
+ }
372
+
373
+ float pixelWorldX = sxSprite - pivot.x + additionalPixelOffset.x;
374
+ float pixelWorldY = sySprite - pivot.y + additionalPixelOffset.y;
375
+ int bufferX = Mathf.FloorToInt(
376
+ pixelWorldX - compositeBufferOriginX
377
+ );
378
+ int bufferY = Mathf.FloorToInt(
379
+ pixelWorldY - compositeBufferOriginY
380
+ );
381
+
382
+ if (
383
+ bufferX < 0
384
+ || bufferX >= compositeBufferWidth
385
+ || bufferY < 0
386
+ || bufferY >= compositeBufferHeight
387
+ )
388
+ {
389
+ continue;
390
+ }
391
+
392
+ int bufferIndex = bufferY * compositeBufferWidth + bufferX;
393
+ Color existingColor = bufferPixels[bufferIndex];
394
+ if (existingColor.a < pixelCutoff)
395
+ {
396
+ existingColor = _backgroundColor;
397
+ }
398
+
399
+ Color blendedColor = Color.Lerp(
400
+ existingColor,
401
+ spritePixelColor,
402
+ layerAlpha
403
+ );
404
+
405
+ bufferPixels[bufferIndex] = blendedColor;
264
406
  }
265
-
266
- Color blendedColor = Color.Lerp(existingColor, pixelColor, alpha);
267
- pixels[index] = blendedColor;
268
407
  }
269
408
  );
270
409
  }
271
410
 
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;
411
+ int finalMinX = int.MaxValue,
412
+ finalMaxX = int.MinValue;
413
+ int finalMinY = int.MaxValue,
414
+ finalMaxY = int.MinValue;
277
415
 
278
416
  Parallel.For(
279
417
  0,
280
- height * width,
281
- inIndex =>
418
+ compositeBufferHeight * compositeBufferWidth,
419
+ bufferIndex =>
282
420
  {
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)
421
+ if (bufferPixels[bufferIndex].a >= pixelCutoff)
294
422
  {
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
- }
423
+ int x = bufferIndex % compositeBufferWidth;
424
+ int y = bufferIndex / compositeBufferWidth;
309
425
 
310
- expectedY = finalMaxY;
311
- while (expectedY < y)
312
- {
313
- expectedY = Interlocked.CompareExchange(ref finalMaxY, y, expectedY);
426
+ int currentVal;
427
+ do
428
+ {
429
+ currentVal = Volatile.Read(ref finalMinX);
430
+ } while (
431
+ x < currentVal
432
+ && Interlocked.CompareExchange(ref finalMinX, x, currentVal)
433
+ != currentVal
434
+ );
435
+ do
436
+ {
437
+ currentVal = Volatile.Read(ref finalMaxX);
438
+ } while (
439
+ x > currentVal
440
+ && Interlocked.CompareExchange(ref finalMaxX, x, currentVal)
441
+ != currentVal
442
+ );
443
+ do
444
+ {
445
+ currentVal = Volatile.Read(ref finalMinY);
446
+ } while (
447
+ y < currentVal
448
+ && Interlocked.CompareExchange(ref finalMinY, y, currentVal)
449
+ != currentVal
450
+ );
451
+ do
452
+ {
453
+ currentVal = Volatile.Read(ref finalMaxY);
454
+ } while (
455
+ y > currentVal
456
+ && Interlocked.CompareExchange(ref finalMaxY, y, currentVal)
457
+ != currentVal
458
+ );
314
459
  }
315
460
  }
316
461
  );
317
462
 
318
463
  if (finalMinX == int.MaxValue)
319
464
  {
465
+ yield return null;
320
466
  continue;
321
467
  }
322
468
 
323
- // Calculate the final width and height of the culled texture
324
469
  int finalWidth = finalMaxX - finalMinX + 1;
325
470
  int finalHeight = finalMaxY - finalMinY + 1;
326
471
 
327
472
  Color[] finalPixels = new Color[finalWidth * finalHeight];
328
- Array.Fill(finalPixels, _backgroundColor);
329
473
 
330
- // Copy the non-transparent pixels from the temporary texture to the final texture
474
+ Array.Fill(finalPixels, _backgroundColor);
331
475
  Parallel.For(
332
476
  0,
333
- finalWidth * finalHeight,
334
- inIndex =>
477
+ finalHeight,
478
+ yFinal =>
335
479
  {
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)
480
+ for (int xFinal = 0; xFinal < finalWidth; ++xFinal)
342
481
  {
343
- return;
482
+ int bufferX = finalMinX + xFinal;
483
+ int bufferY = finalMinY + yFinal;
484
+ Color pixelColor = bufferPixels[
485
+ bufferY * compositeBufferWidth + bufferX
486
+ ];
487
+
488
+ if (pixelColor.a >= pixelCutoff)
489
+ {
490
+ finalPixels[yFinal * finalWidth + xFinal] = pixelColor;
491
+ }
344
492
  }
345
- finalPixels[y * finalWidth + x] = pixelColor;
346
493
  }
347
494
  );
348
495
 
@@ -351,12 +498,11 @@
351
498
  finalHeight,
352
499
  TextureFormat.RGBA32,
353
500
  mipChain: false,
354
- linear: false,
355
- createUninitialized: true
501
+ linear: false
356
502
  );
357
- finalTexture.SetPixels(finalPixels);
358
- finalTexture.Apply(false, false);
359
503
 
504
+ finalTexture.SetPixels(finalPixels);
505
+ finalTexture.Apply(updateMipmaps: false, makeNoLongerReadable: false);
360
506
  yield return finalTexture;
361
507
  }
362
508
  }
@@ -21,7 +21,7 @@
21
21
 
22
22
  [JsonIgnore]
23
23
  [IgnoreDataMember]
24
- private readonly HashSet<string> _availableBools = new HashSet<string>();
24
+ private readonly HashSet<string> _availableBools = new();
25
25
 
26
26
  [JsonIgnore]
27
27
  [IgnoreDataMember]
@@ -53,7 +53,7 @@
53
53
 
54
54
  tImporter.isReadable = isReadable;
55
55
 
56
- TextureImporterPlatformSettings importerSettings = new TextureImporterPlatformSettings
56
+ TextureImporterPlatformSettings importerSettings = new()
57
57
  {
58
58
  resizeAlgorithm = TextureResizeAlgorithm.Bilinear,
59
59
  maxTextureSize = MaxTextureSize,
@@ -54,8 +54,8 @@
54
54
  }
55
55
  else
56
56
  {
57
- ratioX = ((float)tex.width) / newWidth;
58
- ratioY = ((float)tex.height) / newHeight;
57
+ ratioX = (float)tex.width / newWidth;
58
+ ratioY = (float)tex.height / newHeight;
59
59
  }
60
60
 
61
61
  w = tex.width;
@@ -123,7 +123,7 @@
123
123
  int y2 = (yFloor + 1) * w;
124
124
  int yw = y * w2;
125
125
 
126
- for (var x = 0; x < w2; x++)
126
+ for (int x = 0; x < w2; x++)
127
127
  {
128
128
  int xFloor = (int)Mathf.Floor(x * ratioX);
129
129
  float xLerp = x * ratioX - xFloor;
@@ -155,7 +155,7 @@
155
155
  {
156
156
  int thisY = (int)(ratioY * y) * w;
157
157
  int yw = y * w2;
158
- for (var x = 0; x < w2; x++)
158
+ for (int x = 0; x < w2; x++)
159
159
  {
160
160
  newColors[yw + x] = texColors[(int)(thisY + ratioX * x)];
161
161
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "com.wallstop-studios.unity-helpers",
3
- "version": "2.0.0-rc73.7",
3
+ "version": "2.0.0-rc73.8",
4
4
  "displayName": "Unity Helpers",
5
5
  "description": "Various Unity Helper Library",
6
6
  "dependencies": {},
@@ -43,3 +43,4 @@
43
43
 
44
44
 
45
45
 
46
+