Files
blockout/BlockOutReplica/Assets/Scripts/Presentation/OriginalSpriteGeometry.cs
T
2026-10-20 13:21:04 +08:00

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));
}
}