tests/test_limb_bend.cs
using System.Numerics;
using HumanoidMocapX;
using HumanoidMocapX.Core.Motion;
using HumanoidMocapX.Inference;
static class LimbBendTests
{
internal static void Run()
{
// The video shows the left elbow bent 30°; the capture bends it 60°.
var observations = ObservationFixture.Observe(Bent(.5f).ToDocument());
var same = Bent(.5f).ToDocument();
MotionTests.Near(0, LimbBend.Apply(same, observations), .5);
var bent = Bent(1.05f).ToDocument(); var before = Bent(1.05f).ToDocument();
var applied = LimbBend.Apply(bent, observations);
MotionTests.Check(applied > 25, $"Elbow opened only {applied:F1} degrees");
int upper = ObservationFixture.Bone(bent, "UpperArmL"), lower = ObservationFixture.Bone(bent, "LowerArmL"), hand = ObservationFixture.Bone(bent, "HandL");
for (var f = 0; f < bent.Frames.Count; f++)
{
var position = ObservationFixture.Positions(bent, bent.Frames[f], out var rotation, out _);
var angle = Angle(position[upper] - position[lower], position[hand] - position[lower]);
MotionTests.Near(180 - .5 * 180 / Math.PI, angle, 2);
ObservationFixture.Positions(before, before.Frames[f], out var original, out _);
MotionTests.Near(1, Math.Abs(Quaternion.Dot(original[hand], rotation[hand])), 1e-5);
}
// A disagreement this large is a keypoint failure, not posture.
var wild = Bent(1.8f).ToDocument();
MotionTests.Near(0, LimbBend.Apply(wild, observations), .5);
MotionTests.Throws<ArgumentException>(() => LimbBend.Apply(same, observations.AsSpan(1)));
// A knee bent 60° in the video and 20° in the capture folds to it, the foot keeping its attitude.
var seen = Knee(1.05f).ToDocument(); var knee = ObservationFixture.Observe(seen); var straight = Knee(.35f).ToDocument(); var kept = Knee(.35f).ToDocument();
MotionTests.Check(LimbBend.Apply(straight, knee) > 25, "The knee was not folded");
int thigh = ObservationFixture.Bone(straight, "UpperLegL"), shin = ObservationFixture.Bone(straight, "LowerLegL"), foot = ObservationFixture.Bone(straight, "FootL");
for (var f = 0; f < straight.Frames.Count; f++)
{
var position = ObservationFixture.Positions(straight, straight.Frames[f], out var rotation, out _);
// The fixture's rest leg is not quite straight, so the angle to reach is the observed one.
var observed = ObservationFixture.Positions(seen, seen.Frames[f], out _, out _);
MotionTests.Near(Angle(observed[thigh] - observed[shin], observed[foot] - observed[shin]), Angle(position[thigh] - position[shin], position[foot] - position[shin]), 2);
ObservationFixture.Positions(kept, kept.Frames[f], out var original, out _);
MotionTests.Near(1, Math.Abs(Quaternion.Dot(original[foot], rotation[foot])), 1e-5);
}
Console.WriteLine("PASS: limb bend opens an elbow and folds a knee to the angles SAM sees, keeps hand and foot attitude, and leaves matching and implausible limbs alone");
}
/// <summary>The motion fixture with the left elbow bent by <paramref name="angle"/> radians.</summary>
static FlowMotion Bent(float angle)
{
var fixture = MotionTests.Fixture();
// SMPL's left elbow is joint 18; the T-pose arm lies along +x, so a turn about +y bends the forearm forward.
foreach (var pose in fixture.Poses) pose[18 * 3 + 1] = angle;
return fixture;
}
/// <summary>The motion fixture with the left knee bent by <paramref name="angle"/> radians.</summary>
static FlowMotion Knee(float angle)
{
var fixture = MotionTests.Fixture();
// SMPL's left knee is joint 4; a turn about +x swings the shin back.
foreach (var pose in fixture.Poses) pose[4 * 3] = angle;
return fixture;
}
static double Angle(Vector3 a, Vector3 b) => Math.Acos(Math.Clamp(Vector3.Dot(Vector3.Normalize(a), Vector3.Normalize(b)), -1, 1)) * 180 / Math.PI;
}