fix(D.2b): tile the vital-bar middle instead of stretching it
Retail repeats the bar's "fill-tile" graphic at native width (verified: the dat element 0x100000E9 is literally the fill-tile; the engine fills via ImgTex::TileCSI; and a widened side-by-side shows retail tiling, not stretching). acdream was stretching one copy of the middle slice across the whole span, so the bevel/bead pattern smeared as the window widened. UiMeter.DrawHBar now UV-repeats each slice at its NATIVE width: caps span one native width (a single 1:1 copy), the wide middle spans many (it tiles, last copy UV-cropped). This works because the UI textures are already GL_REPEAT- wrapped (TextureCache.UploadRgba8) — the exact mechanism UiNineSlicePanel's chrome border already uses, so the border edges were ALREADY tiling and need no change. One draw call per slice; composes with the existing fill-fraction clip (the partial last tile shows a partial bead). render-vitals-mockup now renders a widened window twice (stretch vs tile) so the difference is verifiable headless. Confirmed the tile repeats seamlessly (no seams). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4e60c03a74
commit
73468be02a
2 changed files with 108 additions and 76 deletions
|
|
@ -35,20 +35,21 @@ public sealed class UiMeter : UiElement
|
|||
public Func<uint, (uint tex, int w, int h)>? SpriteResolve { get; set; }
|
||||
|
||||
// Retail vital bars are a horizontal 3-slice: a fixed-width bevelled left-cap,
|
||||
// a stretched gradient middle, and a fixed-width right-cap. The "back" slice is
|
||||
// the empty track (drawn full width); the "front" slice is the coloured fill
|
||||
// (drawn from the left, grown to the fill fraction — the track owns the right
|
||||
// end, so the fill omits its own right-cap). Ids come from the vitals LayoutDesc
|
||||
// (0x21000014) via tools/dump-vitals-bars; 0 = none.
|
||||
// a TILED gradient middle (the "fill-tile" repeats at native width — it does not
|
||||
// stretch), and a fixed-width right-cap. The "back" slice is the empty track
|
||||
// (drawn full width); the "front" slice is the coloured fill (drawn full-geometry
|
||||
// but CLIPPED to the fill fraction — its own right-cap shows at 100%, the back's
|
||||
// shows through when partial). Ids come from the stacked vitals LayoutDesc
|
||||
// (0x2100006C) via the dump-vitals-layout CLI; 0 = none.
|
||||
/// <summary>Empty-track left-cap RenderSurface id.</summary>
|
||||
public uint BackLeft { get; set; }
|
||||
/// <summary>Empty-track middle (stretched gradient) RenderSurface id.</summary>
|
||||
/// <summary>Empty-track middle (tiled gradient) RenderSurface id.</summary>
|
||||
public uint BackTile { get; set; }
|
||||
/// <summary>Empty-track right-cap RenderSurface id.</summary>
|
||||
public uint BackRight { get; set; }
|
||||
/// <summary>Coloured-fill left-cap RenderSurface id.</summary>
|
||||
public uint FrontLeft { get; set; }
|
||||
/// <summary>Coloured-fill middle (stretched gradient) RenderSurface id.</summary>
|
||||
/// <summary>Coloured-fill middle (tiled gradient) RenderSurface id.</summary>
|
||||
public uint FrontTile { get; set; }
|
||||
/// <summary>Coloured-fill right-cap RenderSurface id.</summary>
|
||||
public uint FrontRight { get; set; }
|
||||
|
|
@ -130,28 +131,36 @@ public sealed class UiMeter : UiElement
|
|||
if (clipW <= 0f) return;
|
||||
float w = Width, h = Height;
|
||||
var (lt, lw, _) = resolve(leftId);
|
||||
var (mt, _, _) = resolve(midId);
|
||||
var (mt, mw, _) = resolve(midId);
|
||||
var (rt, rw, _) = resolve(rightId);
|
||||
|
||||
float capL = lt != 0 ? MathF.Min(lw, w) : 0f;
|
||||
float capR = rt != 0 ? MathF.Min(rw, w - capL) : 0f;
|
||||
float midW = w - capL - capR;
|
||||
|
||||
DrawPiece(ctx, lt, 0f, capL, h, clipW);
|
||||
DrawPiece(ctx, mt, capL, midW, h, clipW);
|
||||
DrawPiece(ctx, rt, w - capR, capR, h, clipW);
|
||||
// Each slice's texture repeats every NATIVE-width px (UV-repeat; the UI
|
||||
// texture is GL_REPEAT-wrapped — TextureCache.UploadRgba8). Caps span their
|
||||
// own native width → a single 1:1 copy. The wide middle spans many native
|
||||
// widths → it TILES, matching retail's "fill-tile" + ImgTex::TileCSI rather
|
||||
// than stretching one copy. (Same UV-repeat the chrome border already uses.)
|
||||
DrawPiece(ctx, lt, 0f, capL, lw, h, clipW);
|
||||
DrawPiece(ctx, mt, capL, midW, mw, h, clipW);
|
||||
DrawPiece(ctx, rt, w - capR, capR, rw, h, clipW);
|
||||
}
|
||||
|
||||
/// <summary>Draw one slice spanning local [<paramref name="pieceX"/>,
|
||||
/// pieceX+<paramref name="pieceW"/>], UV-cropped so nothing past
|
||||
/// <paramref name="clipW"/> shows.</summary>
|
||||
/// <summary>Draw a slice over local [<paramref name="pieceX"/>,
|
||||
/// pieceX+<paramref name="pieceW"/>], with the texture repeating every
|
||||
/// <paramref name="nativeW"/> px (UV-repeat — the UI texture is GL_REPEAT-wrapped).
|
||||
/// Clipped so nothing past <paramref name="clipW"/> shows. For a cap (span == native)
|
||||
/// this is one 1:1 copy; for the wide middle it tiles; a partial last copy is
|
||||
/// UV-cropped.</summary>
|
||||
private static void DrawPiece(
|
||||
UiRenderContext ctx, uint tex, float pieceX, float pieceW, float h, float clipW)
|
||||
UiRenderContext ctx, uint tex, float pieceX, float pieceW, float nativeW, float h, float clipW)
|
||||
{
|
||||
if (tex == 0 || pieceW <= 0f) return;
|
||||
if (tex == 0 || pieceW <= 0f || nativeW <= 0f) return;
|
||||
float visibleW = MathF.Min(pieceW, clipW - pieceX);
|
||||
if (visibleW <= 0f) return;
|
||||
float u1 = visibleW / pieceW; // crop the texture horizontally
|
||||
float u1 = visibleW / nativeW; // >1 ⇒ texture repeats (tiles); ≤1 ⇒ a partial copy
|
||||
ctx.DrawSprite(tex, pieceX, 0f, visibleW, h, 0f, 0f, u1, 1f, Vector4.One);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue