Fix AE001H spritesheet animation
This commit is contained in:
@@ -62,7 +62,8 @@ public class AnimChannelTests
|
||||
vm.Run();
|
||||
var o = vm.Gfx.TryGet(0x1000)!;
|
||||
Assert.True(o.SrcAnim);
|
||||
Assert.Equal(4, o.SrcGridW);
|
||||
Assert.Equal(4, o.SrcFrameCount);
|
||||
Assert.Equal(1, o.SrcColumns);
|
||||
Assert.Equal(2, o.SrcCell);
|
||||
}
|
||||
|
||||
@@ -78,9 +79,9 @@ public class AnimChannelTests
|
||||
public void SetSrcRect_StoresGridCellPeriod()
|
||||
{
|
||||
var g = VisibleObj(0x100);
|
||||
g.SetSrcRect(0x100, gridW: 4, gridH: 1, cell: 2, period: 800);
|
||||
g.SetSrcRect(0x100, frameCount: 4, columns: 1, cell: 2, period: 800);
|
||||
var o = g.TryGet(0x100)!;
|
||||
Assert.Equal(4, o.SrcGridW);
|
||||
Assert.Equal(4, o.SrcFrameCount);
|
||||
Assert.Equal(2, o.SrcCell);
|
||||
Assert.Equal(800, o.SrcPeriod);
|
||||
Assert.True(o.SrcAnim);
|
||||
@@ -91,10 +92,10 @@ public class AnimChannelTests
|
||||
public void SetSrcRect_ClampsGridToAtLeastOne()
|
||||
{
|
||||
var g = VisibleObj(0x100);
|
||||
g.SetSrcRect(0x100, gridW: 0, gridH: 0, cell: 0, period: 0);
|
||||
g.SetSrcRect(0x100, frameCount: 0, columns: 0, cell: 0, period: 0);
|
||||
var o = g.TryGet(0x100)!;
|
||||
Assert.Equal(1, o.SrcGridW);
|
||||
Assert.Equal(1, o.SrcGridH);
|
||||
Assert.Equal(1, o.SrcFrameCount);
|
||||
Assert.Equal(1, o.SrcColumns);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -8,7 +8,7 @@ public class AnimInterpolatorTests
|
||||
{
|
||||
var g = new GfxState();
|
||||
g.SetSurface(1, resId: 5, colorKey: -1);
|
||||
g.BindDraw(handle, 1, 0, 0, 256, 64, 100, 100); // 256x64 sheet, base pos (100,100)
|
||||
g.BindDraw(handle, 1, 0, 0, 64, 64, 100, 100); // one 64x64 cell, base pos (100,100)
|
||||
return g;
|
||||
}
|
||||
|
||||
@@ -24,22 +24,24 @@ public class AnimInterpolatorTests
|
||||
public void SrcRect_StaticCell_SelectsSubRect()
|
||||
{
|
||||
var g = VisibleObj(0x100);
|
||||
g.SetSrcRect(0x100, gridW: 4, gridH: 1, cell: 2, period: 0); // static cell 2 of 4 across 256px
|
||||
g.SetSrcRect(0x100, frameCount: 4, columns: 4, cell: 2, period: 0);
|
||||
var ro = g.SnapshotVisibleObjects(0).Single();
|
||||
Assert.Equal(128, ro.SrcX); // cell 2 * 64
|
||||
Assert.Equal(64, ro.W);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SrcRect_Animated_PingPongsCellAcrossGrid()
|
||||
public void SrcRect_Animated_AdvancesOneCellPerPeriodAndWraps()
|
||||
{
|
||||
var g = VisibleObj(0x100);
|
||||
g.SetSrcRect(0x100, gridW: 4, gridH: 1, cell: 0, period: 1000);
|
||||
g.SetSrcRect(0x100, frameCount: 4, columns: 4, cell: 0, period: 100);
|
||||
g.SnapshotVisibleObjects(0); // seeds start=0
|
||||
var half = g.SnapshotVisibleObjects(500).Single();
|
||||
Assert.Equal(192, half.SrcX); // t=1 -> last cell (3) * 64
|
||||
var back = g.SnapshotVisibleObjects(1000).Single();
|
||||
Assert.Equal(0, back.SrcX); // ping-ponged back to cell 0
|
||||
var second = g.SnapshotVisibleObjects(100).Single();
|
||||
Assert.Equal(64, second.SrcX);
|
||||
var fourth = g.SnapshotVisibleObjects(300).Single();
|
||||
Assert.Equal(192, fourth.SrcX);
|
||||
var wrapped = g.SnapshotVisibleObjects(400).Single();
|
||||
Assert.Equal(0, wrapped.SrcX);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -82,6 +82,27 @@ public class GfxAnimationTests
|
||||
Assert.True(rotation.HasActiveVisualPresentation(1000));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LoopingSpritesheet_PreservesCellSize_AdvancesRowMajor_AndWraps()
|
||||
{
|
||||
var g = new GfxState();
|
||||
g.SetSurface(4, 0x37, -1);
|
||||
g.BindDraw(0xcf3a, 4, 0, 0, 200, 200, 300, 100); // AE001H: 800x400, eight 200x200 cells
|
||||
g.SetSrcRect(0xcf3a, frameCount: 8, columns: 4, cell: 0, period: 100);
|
||||
|
||||
var first = g.SnapshotVisibleObjects(1000).Single();
|
||||
var second = g.SnapshotVisibleObjects(1100).Single();
|
||||
var fifth = g.SnapshotVisibleObjects(1400).Single();
|
||||
var eighth = g.SnapshotVisibleObjects(1700).Single();
|
||||
var wrapped = g.SnapshotVisibleObjects(1800).Single();
|
||||
|
||||
Assert.Equal((0, 0, 200, 200), (first.SrcX, first.SrcY, first.W, first.H));
|
||||
Assert.Equal((200, 0, 200, 200), (second.SrcX, second.SrcY, second.W, second.H));
|
||||
Assert.Equal((0, 200, 200, 200), (fifth.SrcX, fifth.SrcY, fifth.W, fifth.H));
|
||||
Assert.Equal((600, 200, 200, 200), (eighth.SrcX, eighth.SrcY, eighth.W, eighth.H));
|
||||
Assert.Equal((0, 0, 200, 200), (wrapped.SrcX, wrapped.SrcY, wrapped.W, wrapped.H));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OneShotRotation_SharesMatrixClockAndMatchesNativeSample()
|
||||
{
|
||||
|
||||
@@ -71,9 +71,10 @@ public sealed class GfxState
|
||||
// fades from a static mode-0 0x203 such as a CG initialized with 0x00ffffff (opaque identity).
|
||||
public bool OneShotColorBlend;
|
||||
|
||||
// ---- src-rect / spritesheet-cell channel (ops 0x239 static cell, 0x231 animate). Interpolator
|
||||
// SRC-RECT SCROLL channel: period obj+0x230, start obj+0x21c, grid obj+0x238/0x23c. ----
|
||||
public long SrcGridW = 1, SrcGridH = 1, SrcCell, SrcPeriod, SrcStart = -1;
|
||||
// ---- src-rect / spritesheet-cell channel (ops 0x239 one-shot cell, 0x231 looping animation).
|
||||
// Native obj+0x238 is the TOTAL FRAME COUNT and +0x23c is the COLUMN COUNT. Each frame keeps
|
||||
// draw-texture's original source-rect size; those operands are not a grid to divide it by. ----
|
||||
public long SrcFrameCount = 1, SrcColumns = 1, SrcCell, SrcPeriod, SrcStart = -1;
|
||||
public bool SrcAnim;
|
||||
// ---- animated color/glow channel (op 0x232). Interpolator COLOR channel: period obj+0x220,
|
||||
// start obj+0x20c, target obj+0x240 — PING-PONG (distinct from static 0x202/0x203). ----
|
||||
@@ -158,7 +159,7 @@ public sealed class GfxState
|
||||
OneShotColorTarget = s.OneShotColorTarget, ColorDelayMs = s.ColorDelayMs,
|
||||
ColorDurationMs = s.ColorDurationMs, OneShotColorEnabled = s.OneShotColorEnabled,
|
||||
OneShotColorBlend = s.OneShotColorBlend,
|
||||
SrcGridW = s.SrcGridW, SrcGridH = s.SrcGridH, SrcCell = s.SrcCell,
|
||||
SrcFrameCount = s.SrcFrameCount, SrcColumns = s.SrcColumns, SrcCell = s.SrcCell,
|
||||
SrcPeriod = s.SrcPeriod, SrcStart = s.SrcStart, SrcAnim = s.SrcAnim,
|
||||
ColorPeriod = s.ColorPeriod, ColorStart = s.ColorStart, ColorTarget = s.ColorTarget,
|
||||
ColorAnim = s.ColorAnim, SourceSlot = s.SourceSlot, SrcRect = s.SrcRect, Visible = s.Visible,
|
||||
@@ -282,14 +283,16 @@ public sealed class GfxState
|
||||
lock (_lock) GetOrCreate(handle).StaticColorMode = mode;
|
||||
}
|
||||
|
||||
/// <summary>Ops 0x239 (static cell, period=0) / 0x231 (animate, period>0): set the spritesheet grid +
|
||||
/// the visible cell. When animated, the interpolator ping-pongs the cell across the grid over the period.</summary>
|
||||
public void SetSrcRect(long handle, long gridW, long gridH, long cell, long period)
|
||||
/// <summary>Ops 0x239 (one-shot cell endpoint, period=0 in this retained model) / 0x231 (looping):
|
||||
/// set total frame count, column count, and visible cell. Every frame retains draw-texture's source
|
||||
/// rectangle dimensions. For 0x231, <paramref name="period"/> is milliseconds per frame.</summary>
|
||||
public void SetSrcRect(long handle, long frameCount, long columns, long cell, long period)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var o = GetOrCreate(handle);
|
||||
o.SrcGridW = gridW < 1 ? 1 : gridW; o.SrcGridH = gridH < 1 ? 1 : gridH;
|
||||
o.SrcFrameCount = frameCount < 1 ? 1 : frameCount;
|
||||
o.SrcColumns = columns < 1 ? 1 : columns;
|
||||
o.SrcCell = cell; o.SrcPeriod = period; o.SrcStart = -1; o.SrcAnim = true;
|
||||
}
|
||||
}
|
||||
@@ -555,23 +558,18 @@ public sealed class GfxState
|
||||
blend = BlendKind.Alpha;
|
||||
}
|
||||
|
||||
// ---- src-rect: pick the spritesheet cell (static or ping-ponged across the grid) ----
|
||||
// ---- src-rect: preserve cell dimensions and offset it row-major through the sheet ----
|
||||
int srcX = o.SrcRect.X, srcY = o.SrcRect.Y, w = o.SrcRect.W, h = o.SrcRect.H;
|
||||
if (o.SrcAnim && o.SrcGridW >= 1)
|
||||
if (o.SrcAnim && o.SrcFrameCount >= 1)
|
||||
{
|
||||
int cellW = (int)(o.SrcRect.W / o.SrcGridW);
|
||||
int cellH = o.SrcGridH >= 1 ? (int)(o.SrcRect.H / o.SrcGridH) : o.SrcRect.H;
|
||||
long cell = o.SrcCell;
|
||||
if (o.SrcPeriod > 0)
|
||||
{
|
||||
if (o.SrcStart < 0) o.SrcStart = nowMs;
|
||||
double t = PingPongWeight(nowMs, o.SrcStart, o.SrcPeriod);
|
||||
cell = (long)System.Math.Round(t * (o.SrcGridW - 1));
|
||||
cell = ((nowMs - o.SrcStart) / o.SrcPeriod) % o.SrcFrameCount;
|
||||
}
|
||||
long cols = o.SrcGridW;
|
||||
srcX = o.SrcRect.X + (int)(cell % cols) * cellW;
|
||||
srcY = o.SrcRect.Y + (int)(cell / cols) * cellH;
|
||||
w = cellW; h = cellH;
|
||||
srcX = o.SrcRect.X + (int)(cell % o.SrcColumns) * o.SrcRect.W;
|
||||
srcY = o.SrcRect.Y + (int)(cell / o.SrcColumns) * o.SrcRect.H;
|
||||
}
|
||||
|
||||
// One-shot matrix channels: hold current through delay, then linearly sample current -> target.
|
||||
|
||||
@@ -387,9 +387,9 @@ public sealed class VirtualMachine
|
||||
case "u00421DD0": // 0x22f set-position: (handle)(op2)(x)(y)(z) -> base position (direct set)
|
||||
case "u004219E0": // 0x229 set-position2: same shape, direct position
|
||||
Gfx.GetOrCreate(Read(a[0])).V24 = (Read(a[2]), Read(a[3]), Read(a[4])); return pc + 1;
|
||||
case "u004223C0": // 0x239 spritesheet cell: (handle)(p3)(p4)(gridW)(gridH)(cell) — static cell
|
||||
case "u004223C0": // 0x239 spritesheet cell: (handle)(delay)(duration)(frame count)(columns)(cell)
|
||||
Gfx.SetSrcRect(Read(a[0]), Read(a[3]), Read(a[4]), Read(a[5]), 0); return pc + 1;
|
||||
case "u00421EA0": // 0x231 anim spritesheet: (handle)(period)(gridW)(gridH) — ping-pong the cell
|
||||
case "u00421EA0": // 0x231 looping spritesheet: (handle)(ms per frame)(frame count)(columns)
|
||||
Gfx.SetSrcRect(Read(a[0]), Read(a[2]), Read(a[3]), 0, Read(a[1])); return pc + 1;
|
||||
case "u00421EF0": // 0x232 anim color/glow: (handle)(period)(alpha)(color) — ping-pong the color
|
||||
Gfx.SetColorAnim(Read(a[0]), Read(a[1]), GfxState.PackColor(Read(a[2]), Read(a[3]))); return pc + 1;
|
||||
|
||||
Reference in New Issue
Block a user