104 lines
5.7 KiB
C#
104 lines
5.7 KiB
C#
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
|
|
namespace BlockOutReplica.Presentation
|
|
{
|
|
public enum OriginalSpriteLayout { Auto, Full, NineSlice, MirroredHalf }
|
|
|
|
/// <summary>Pixel-space atlas mapping shared by IMGUI and geometry regression checks.</summary>
|
|
public static class OriginalSpriteGeometry
|
|
{
|
|
public struct Segment
|
|
{
|
|
public Rect Target, Source;
|
|
public Segment(Rect target, Rect source) {Target = target; Source = source;}
|
|
}
|
|
|
|
// Native GamePlayScene SpriteRenderer.size; every ancestor has unit localScale.
|
|
// These are expanded widget sizes, NOT the tiny packed Sprite.rect sizes.
|
|
public static bool TryGetNativeBackingSize(string name, out Vector2 size)
|
|
{
|
|
switch (name)
|
|
{
|
|
case "Ingame_Currency_Background": size = new Vector2(1.7220167f, .62f); return true;
|
|
case "Ingame_Level_Background": size = new Vector2(2.095544f, .62f); return true;
|
|
case "Ingame_Timer_Background": size = new Vector2(2.7185862f, .95f); return true;
|
|
case "Ingame_Booster_Counter_Purple": size = new Vector2(1.4110428f, .56f); return true;
|
|
default: size = Vector2.zero; return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>Keep the existing center/height; restore the actual native widget aspect.</summary>
|
|
public static Rect FitNativeBacking(Rect requested, string spriteName)
|
|
{
|
|
if (!TryGetNativeBackingSize(spriteName, out Vector2 size)) return requested;
|
|
return FitNativeAspect(requested, size);
|
|
}
|
|
|
|
public static Rect FitNativeAspect(Rect requested, Vector2 nativeSize)
|
|
{
|
|
if (nativeSize.x <= 0 || nativeSize.y <= 0) return requested;
|
|
float width = requested.height * nativeSize.x / nativeSize.y;
|
|
return new Rect(requested.center.x - width * .5f, requested.y, width, requested.height);
|
|
}
|
|
|
|
public static Rect FitIcon(Rect target, Vector2 sourceSize)
|
|
{
|
|
if (sourceSize.x <= 0 || sourceSize.y <= 0) return target;
|
|
float scale = Mathf.Min(target.width / sourceSize.x, target.height / sourceSize.y);
|
|
var size = sourceSize * scale;
|
|
return new Rect(target.center.x - size.x * .5f, target.center.y - size.y * .5f, size.x, size.y);
|
|
}
|
|
|
|
public static List<Segment> Build(Rect source, Vector4 border, Rect target, OriginalSpriteLayout layout, float pixelsPerSourcePixel = 0)
|
|
{
|
|
var result = new List<Segment>(18);
|
|
if (source.width <= 0 || source.height <= 0 || target.width <= 0 || target.height <= 0) return result;
|
|
if (layout == OriginalSpriteLayout.MirroredHalf)
|
|
{
|
|
// One shared center boundary, including at fractional DPI, prevents gaps.
|
|
float middle = Mathf.Round(target.center.x);
|
|
Add(result, source, border, Rect.MinMaxRect(target.xMin, target.yMin, middle, target.yMax), true, false, pixelsPerSourcePixel);
|
|
Add(result, source, border, Rect.MinMaxRect(middle, target.yMin, target.xMax, target.yMax), true, true, pixelsPerSourcePixel);
|
|
}
|
|
else Add(result, source, border, target, layout == OriginalSpriteLayout.NineSlice, false, pixelsPerSourcePixel);
|
|
return result;
|
|
}
|
|
|
|
private static void Add(List<Segment> result, Rect source, Vector4 border, Rect target, bool slice, bool mirror, float pixelsPerSourcePixel)
|
|
{
|
|
if (!slice || border == Vector4.zero)
|
|
{
|
|
result.Add(new Segment(PixelRect(target), new Rect(mirror ? source.xMax : source.x, source.y,
|
|
mirror ? -source.width : source.width, source.height)));
|
|
return;
|
|
}
|
|
// Keep original corner proportions. Never cap every border at 25% of its target:
|
|
// that squeezes 90px panel corners into thin strips and stretches their highlights.
|
|
float pixelScale = pixelsPerSourcePixel > 0 ? pixelsPerSourcePixel : Mathf.Min(target.width / source.width, target.height / source.height);
|
|
float left = border.x * pixelScale, right = border.z * pixelScale;
|
|
float top = border.w * pixelScale, bottom = border.y * pixelScale;
|
|
float fitX = left + right > target.width ? target.width / (left + right) : 1;
|
|
float fitY = top + bottom > target.height ? target.height / (top + bottom) : 1;
|
|
left *= fitX; right *= fitX; top *= fitY; bottom *= fitY;
|
|
if (mirror) {float swap = left; left = right; right = swap;}
|
|
float[] dx = {target.x, target.x + left, target.xMax - right, target.xMax};
|
|
float[] dy = {target.y, target.y + top, target.yMax - bottom, target.yMax};
|
|
float[] sx = {0, border.x, source.width - border.z, source.width};
|
|
float[] sy = {source.height, source.height - border.w, border.y, 0};
|
|
for (int row = 0; row < 3; row++) for (int column = 0; column < 3; column++)
|
|
{
|
|
int index = mirror ? 2 - column : column;
|
|
float sw = sx[index + 1] - sx[index], sh = sy[row] - sy[row + 1];
|
|
Rect destination = PixelRect(Rect.MinMaxRect(dx[column], dy[row], dx[column + 1], dy[row + 1]));
|
|
if (sw <= 0 || sh <= 0 || destination.width <= 0 || destination.height <= 0) continue;
|
|
result.Add(new Segment(destination, new Rect(source.x + (mirror ? sx[index + 1] : sx[index]),
|
|
source.y + sy[row + 1], mirror ? -sw : sw, sh)));
|
|
}
|
|
}
|
|
|
|
private static Rect PixelRect(Rect rect) => Rect.MinMaxRect(Mathf.Round(rect.xMin), Mathf.Round(rect.yMin),
|
|
Mathf.Round(rect.xMax), Mathf.Round(rect.yMax));
|
|
}
|
|
}
|