tests/test_root_anchor.cs
using HumanoidMocapX;
using HumanoidMocapX.Inference;
static class RootAnchorTests
{
const float Drift = .5f;
internal static void Run()
{
var still = Long(MotionTests.Fixture()).ToDocument(); var observations = ObservationFixture.Observe(still);
var drifting = Drifting().ToDocument(); var rotations = ObservationFixture.StaticCamera(drifting);
var shift = RootAnchor.Apply(drifting, observations, rotations, 0);
MotionTests.Check(shift is > .3 and < .55, $"Expected the anchor to undo most of the drift, shifted {shift}");
var last = drifting.Frames[^1].Positions[0]; var start = drifting.Frames[0].Positions[0];
MotionTests.Check(Math.Abs(last[0] - still.Frames[^1].Positions[0][0]) < .03, $"Root still drifts: {last[0]}");
MotionTests.Near(still.Frames[^1].Positions[0][1], last[1], 1e-6);
MotionTests.Near(0, start[0] - still.Frames[0].Positions[0][0], .02);
var turning = ObservationFixture.StaticCamera(still);
for (var f = 0; f < still.Frames.Count; f++)
{
var angle = f * .002f;
turning[f * 9] = MathF.Cos(angle); turning[f * 9 + 2] = MathF.Sin(angle); turning[f * 9 + 6] = -MathF.Sin(angle); turning[f * 9 + 8] = MathF.Cos(angle);
}
var untouched = Drifting().ToDocument();
MotionTests.Check(RootAnchor.Apply(untouched, observations, turning, 0) is null, "A turning camera must not anchor the root");
MotionTests.Check(RootAnchor.Apply(untouched, observations, rotations, RootAnchor.MaxCameraTravel * 2) is null, "A travelling camera must not anchor the root");
MotionTests.Check(RootAnchor.Apply(untouched, observations, rotations, float.NaN) is null, "Unknown camera travel must not anchor the root");
MotionTests.Near(Drift, untouched.Frames[^1].Positions[0][0], 1e-6);
MotionTests.Throws<ArgumentException>(() => RootAnchor.Apply(untouched, observations, rotations.AsSpan(9), 0));
Console.WriteLine("PASS: root anchor removes drift the camera does not see, keeps height and start, and leaves turning, travelling or unknown cameras alone");
}
/// <summary>The motion fixture over 10 s, wandering sideways by <see cref="Drift"/> metres.</summary>
static FlowMotion Drifting()
{
var fixture = Long(MotionTests.Fixture());
for (var f = 0; f < fixture.Translations.Length; f++) fixture.Translations[f][0] = Drift * f / (fixture.Translations.Length - 1);
return fixture;
}
static FlowMotion Long(FlowMotion fixture)
{
const int Frames = 300;
fixture.Poses = Enumerable.Range(0, Frames).Select(_ => new float[156]).ToArray();
fixture.Translations = Enumerable.Range(0, Frames).Select(_ => new float[3]).ToArray();
fixture.Contacts = Enumerable.Range(0, Frames).Select(_ => new[] { 1f, 1f, 1f, 1f }).ToArray();
return fixture;
}
}