142 lines
6.1 KiB
C#
142 lines
6.1 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Age.Engine.Model;
|
|
using Age.Engine.Sys4;
|
|
using Age.Engine.Vm;
|
|
using Xunit;
|
|
|
|
public class GfxAnimationTests
|
|
{
|
|
// ---- Native independent matrix channels + separate cyclic rotation (see engine-re.md). ----
|
|
|
|
[Fact]
|
|
public void ScaleAndTranslationChannels_AreIndependent()
|
|
{
|
|
var g = new GfxState();
|
|
g.SetScaleChannel(0x1000, delayMs: 7, durationMs: 900, percent: (200, 50, 100));
|
|
g.SetTranslationChannel(0x1000, delayMs: 9, durationMs: 1200, target: (80, -25, 6));
|
|
var o = g.TryGet(0x1000)!;
|
|
Assert.Equal((2.0, 0.5, 1.0), o.ScaleTarget);
|
|
Assert.Equal((80.0, -25.0, 6.0), o.TranslationTarget);
|
|
Assert.Equal((7, 900), (o.ScaleDelayMs, o.ScaleDurationMs));
|
|
Assert.Equal((9, 1200), (o.TranslationDelayMs, o.TranslationDurationMs));
|
|
Assert.True(o.ScaleEnabled);
|
|
Assert.True(o.TranslationEnabled);
|
|
}
|
|
|
|
[Fact]
|
|
public void RotationCycle_DoesNotOverwriteMatrixChannels()
|
|
{
|
|
var g = new GfxState();
|
|
g.SetScaleChannel(0x1000, 0, 100, (150, 150, 100));
|
|
g.SetTranslationChannel(0x1000, 0, 100, (10, 20, 0));
|
|
g.SetRotationCycle(0x1000, periodMs: 30, axis: (0, 0, 5));
|
|
var o = g.TryGet(0x1000)!;
|
|
Assert.Equal((1.5, 1.5, 1.0), o.ScaleTarget);
|
|
Assert.Equal((10.0, 20.0, 0.0), o.TranslationTarget);
|
|
Assert.Equal((0L, 0L, 5L), o.RotationAxis);
|
|
Assert.Equal(30, o.RotationPeriodMs);
|
|
Assert.True(o.RotationEnabled);
|
|
}
|
|
|
|
[Fact]
|
|
public void SetAnimClock_SetsGlobalDurationAndBumpsClockGeneration()
|
|
{
|
|
var g = new GfxState();
|
|
var cg0 = g.AnimClockGeneration;
|
|
g.SetAnimClock(400);
|
|
Assert.Equal(400, g.AnimClockDurationTicks);
|
|
Assert.Equal(cg0 + 1, g.AnimClockGeneration);
|
|
}
|
|
|
|
// ---- Tasks 3-5: VM dispatch + snapshot (synthesized scenes) ----
|
|
|
|
private static OpcodeTable T() => OpcodeTableJson.Load(Paths.OpcodesJson);
|
|
private static Operand G(int a) => new(3, a);
|
|
private static Operand I(long v) => new(0, v);
|
|
private static (int, Operand[]) MovGI(int d, long v) => (0x55, new[] { G(d), I(v) });
|
|
private static (int, Operand[]) Exit() => (0x2, System.Array.Empty<Operand>());
|
|
|
|
[Fact]
|
|
public void MatrixOpcodeDispatch_RecordsSeparateChannels()
|
|
{
|
|
var t = T();
|
|
// 0x220 sets translation; 0x21e sets scale percentages without overwriting it.
|
|
var scene = ScriptAssembler.Assemble(t, "ANIM", new List<(int, Operand[])>
|
|
{
|
|
MovGI(1, 0x1000), MovGI(2, 7), MovGI(3, 9), MovGI(4, 800), MovGI(5, 500), MovGI(6, 0),
|
|
(0x220, new[] { G(1), G(2), G(3), G(4), G(5), G(6) }),
|
|
MovGI(4, 200), MovGI(5, 50), MovGI(6, 100),
|
|
(0x21e, new[] { G(1), G(2), G(3), G(4), G(5), G(6) }),
|
|
Exit(),
|
|
}, System.Array.Empty<string>());
|
|
var vm = new VirtualMachine(scene, t, new RecordingHost());
|
|
vm.Run();
|
|
var o = vm.Gfx.TryGet(0x1000)!;
|
|
Assert.Equal((800.0, 500.0, 0.0), o.TranslationTarget);
|
|
Assert.Equal((2.0, 0.5, 1.0), o.ScaleTarget);
|
|
Assert.True(o.TranslationEnabled);
|
|
Assert.True(o.ScaleEnabled);
|
|
}
|
|
|
|
[Fact]
|
|
public void RotationCycleAndClock_DispatchRemainSeparate()
|
|
{
|
|
var t = T();
|
|
// The legacy anim-start name is a rotation period+axis; 0x238 remains its own clock service.
|
|
var scene = ScriptAssembler.Assemble(t, "ANIM", new List<(int, Operand[])>
|
|
{
|
|
MovGI(1, 0x1000), MovGI(2, 30), MovGI(3, 0), MovGI(4, 0), MovGI(5, 5), MovGI(6, 400),
|
|
(0x234, new[] { G(1), G(2), G(3), G(4), G(5) }),
|
|
(0x238, new[] { G(6) }),
|
|
Exit(),
|
|
}, System.Array.Empty<string>());
|
|
var vm = new VirtualMachine(scene, t, new RecordingHost());
|
|
vm.Run();
|
|
var o = vm.Gfx.TryGet(0x1000)!;
|
|
Assert.Equal((0L, 0L, 5L), o.RotationAxis);
|
|
Assert.Equal(30, o.RotationPeriodMs);
|
|
Assert.True(o.RotationEnabled);
|
|
Assert.Equal((1.0, 1.0, 1.0), o.ScaleCurrent);
|
|
Assert.Equal((0.0, 0.0, 0.0), o.TranslationCurrent);
|
|
Assert.Equal(400, vm.Gfx.AnimClockDurationTicks);
|
|
Assert.Equal(1, vm.Gfx.AnimClockGeneration);
|
|
}
|
|
|
|
[Fact]
|
|
public void SnapshotSamplesDelayedMatrixChannels_WithoutUsingZAsOpacity()
|
|
{
|
|
var g = new GfxState();
|
|
g.SetSurface(4, 0x25, -1);
|
|
g.BindDraw(0xA, 4, 0, 0, 100, 80, 120, 70);
|
|
g.GetOrCreate(0xA).V18 = (100, 50, 0);
|
|
g.SetScaleChannel(0xA, delayMs: 100, durationMs: 200, percent: (200, 50, 300));
|
|
g.SetTranslationChannel(0xA, delayMs: 100, durationMs: 200, target: (40, -10, 99));
|
|
g.SetRotationCycle(0xA, periodMs: 30, axis: (0, 0, 5));
|
|
|
|
var start = g.SnapshotVisibleObjects(1000).Single();
|
|
Assert.Equal((1.0, 1.0, 1.0), (start.Transform.ScaleX, start.Transform.ScaleY, start.Transform.ScaleZ));
|
|
Assert.Equal((0.0, 0.0, 0.0),
|
|
(start.Transform.TranslateX, start.Transform.TranslateY, start.Transform.TranslateZ));
|
|
|
|
var held = g.SnapshotVisibleObjects(1100).Single();
|
|
Assert.Equal((1.0, 1.0, 1.0), (held.Transform.ScaleX, held.Transform.ScaleY, held.Transform.ScaleZ));
|
|
Assert.Equal((0.0, 0.0, 0.0),
|
|
(held.Transform.TranslateX, held.Transform.TranslateY, held.Transform.TranslateZ));
|
|
|
|
var halfway = g.SnapshotVisibleObjects(1200).Single();
|
|
Assert.Equal((1.5, 0.75, 2.0),
|
|
(halfway.Transform.ScaleX, halfway.Transform.ScaleY, halfway.Transform.ScaleZ));
|
|
Assert.Equal((20.0, -5.0, 49.5),
|
|
(halfway.Transform.TranslateX, halfway.Transform.TranslateY, halfway.Transform.TranslateZ));
|
|
Assert.Equal(255, halfway.Alpha);
|
|
|
|
var done = g.SnapshotVisibleObjects(1300).Single();
|
|
Assert.Equal((2.0, 0.5, 3.0), (done.Transform.ScaleX, done.Transform.ScaleY, done.Transform.ScaleZ));
|
|
Assert.Equal((40.0, -10.0, 99.0),
|
|
(done.Transform.TranslateX, done.Transform.TranslateY, done.Transform.TranslateZ));
|
|
Assert.True(done.Rotation.Enabled);
|
|
Assert.Equal(255, done.Alpha); // scale-Z=3, translation-Z=99, rotation-axis-Z=5: still opaque
|
|
}
|
|
}
|