tests/test_head_pitch.cs
using System.Numerics;
using HumanoidMocapX;
using HumanoidMocapX.Core.Motion;
using HumanoidMocapX.Inference;
static class HeadPitchTests
{
internal static void Run()
{
var nodding = Nodding(.35f).ToDocument(); var observations = ObservationFixture.Observe(nodding);
var same = Nodding(.35f).ToDocument();
MotionTests.Near(0, HeadPitch.Apply(same, observations), .01);
var level = MotionTests.Fixture().ToDocument();
var applied = HeadPitch.Apply(level, observations);
MotionTests.Near(.35 * 180 / Math.PI, applied, .1);
var head = ObservationFixture.Bone(level, "Head"); var neck = ObservationFixture.Bone(level, "Neck");
for (var f = 0; f < level.Frames.Count; f++)
{
ObservationFixture.Positions(level, level.Frames[f], out var rotation, out _);
ObservationFixture.Positions(nodding, nodding.Frames[f], out var expected, out _);
MotionTests.Near(1, Math.Abs(Quaternion.Dot(expected[head], rotation[head])), 1e-5);
// The neck carries half of the nod.
MotionTests.Near(.35 / 2, 2 * Math.Acos(Math.Min(1, Math.Abs(MotionDocument.Q(level.Frames[f].Rotations[neck]).W))), 1e-3);
}
var wild = Nodding(1.2f).ToDocument(); var untouched = MotionTests.Fixture().ToDocument();
MotionTests.Near(0, HeadPitch.Apply(untouched, ObservationFixture.Observe(wild)));
MotionTests.Throws<ArgumentException>(() => HeadPitch.Apply(untouched, observations.AsSpan(1)));
Console.WriteLine("PASS: head pitch reaches the observed nod across neck and head, keeps matching heads and ignores implausible faces");
}
/// <summary>The motion fixture with the head nodded forward by <paramref name="angle"/> radians.</summary>
static FlowMotion Nodding(float angle)
{
var fixture = MotionTests.Fixture();
// SMPL's head is joint 15; positive rotation about +x pitches the face down.
foreach (var pose in fixture.Poses) pose[15 * 3] = angle;
return fixture;
}
}