dev/HumanoidRetargeter.Tests/Solve/GroundedLegDirectionTests.cs
using System.Numerics;
using System.Text;
using HumanoidRetargeter.Formats.Bvh;
using HumanoidRetargeter.Maths;
using HumanoidRetargeter.Mapping;
using HumanoidRetargeter.Skeleton;
using HumanoidRetargeter.Target;
using Xunit;
using Skel = HumanoidRetargeter.Skeleton.Skeleton;
namespace HumanoidRetargeter.Tests.Solve;
public class GroundedLegDirectionTests
{
[Theory]
[InlineData(20f)]
[InlineData(-20f)]
public void GroundingDoesNotTiltPlantedLegsWithTargetTorso(float lean)
{
var bytes = Encoding.UTF8.GetBytes(WalkFixture.SyntheticWalkBvh());
var source = BvhImporter.Import(bytes);
var definitions = source.Skeleton.Bones.Select(b =>
{
var rest = b.RestLocal;
if (b.Name == "mixamorig:Spine")
rest.Rot = Quaternion.CreateFromAxisAngle(Vector3.UnitX, lean * MathF.PI / 180f);
return new BoneDefinition(b.Name, b.ParentIndex < 0 ? null : source.Skeleton[b.ParentIndex].Name, rest);
});
var skeleton = Skel.Create(definitions.ToArray());
var (map, _) = Retargeter.ResolveMapping(skeleton);
var target = new RetargetTargetSpec { Rig = TargetRig.FromSkeleton(skeleton, map), VmdlScale = RetargetTargetSpec.SboxSourceScale };
float PlantedLegAngle(bool cleanup)
{
var result = Retargeter.Convert(new RetargetRequest
{
SourceData = bytes, SourceFileName = "walk.bvh", FootPlantCleanup = cleanup,
}, target);
Assert.True(result.Success);
var world = new Pose(result.Clips[0].SolvedFrames![7]).ToWorld(skeleton);
var direction = world[map.RoleToBone[BoneRole.FootR]].Pos
- world[map.RoleToBone[BoneRole.LowerLegR]].Pos;
return MathQ.AngleBetween(direction, -Vector3.UnitY) * 180f / MathF.PI;
}
Assert.True(PlantedLegAngle(false) > 10f); // Repro: torso lean contaminates the stance.
Assert.True(PlantedLegAngle(true) < 1f);
}
}