tests/CitizenAnimationSetup/SmartPortGripReachTests.cs
using System.Numerics;
using HumanoidRetargeter.Cleanup;
using HumanoidRetargeter.Maths;
using HumanoidRetargeter.Skeleton;
using HumanoidRetargeter.Target;
using HumanoidRetargeter.Tests.Skeleton;
using Xunit;
using SkeletonModel = HumanoidRetargeter.Skeleton.Skeleton;

namespace HumanoidRetargeter.Tests.Target;

public class SmartPortGripReachTests
{
    [Fact]
    public void SharedGripFitsShorterArmsWithoutStretching()
    {
        var original = TargetRig.Load(TargetRigGenerator.Generate(File.ReadAllText(SkeletonTests.FixturePath("rig_human_male.json")))).Skeleton;
        var definitions = original.Bones.Select(b => new BoneDefinition(b.Name,
            b.ParentIndex < 0 ? null : original[b.ParentIndex].Name, b.RestLocal)).ToList();
        foreach (var side in new[] { "L", "R" }) definitions.Add(new("grip_" + side, "hand_" + side, XForm.Identity));
        definitions.Add(new("support_goal", "hand_R", XForm.Identity));
        var source = SkeletonModel.Create(definitions);
        var target = SkeletonModel.Create(original.Bones.Select(b => new BoneDefinition(b.Name,
            b.ParentIndex < 0 ? null : original[b.ParentIndex].Name,
            new XForm(b.RestLocal.Pos * (b.Name is "arm_lower_L" or "hand_L" ? .55f : 1), b.RestLocal.Rot))).ToArray());
        var pose = source.Bones.Select(b => b.RestLocal).ToArray();
        var left = source.IndexOf("arm_upper_L"); var right = source.IndexOf("arm_upper_R");
        var center = (source.RestWorld[left].Pos + source.RestWorld[right].Pos) * .5f;
        var up = Vector3.Normalize(source.RestWorld[source.IndexOf("head")].Pos - source.RestWorld[source.IndexOf("pelvis")].Pos);
        var lateral = Vector3.Normalize(source.RestWorld[right].Pos - source.RestWorld[left].Pos);
        var forward = Vector3.Normalize(Vector3.Cross(up, lateral));
        var reach = source[source.IndexOf("arm_lower_R")].RestLocal.Pos.Length() + source[source.IndexOf("hand_R")].RestLocal.Pos.Length();
        foreach (var side in new[] { "L", "R" })
        {
            var chain = new LimbChain { Upper = source.IndexOf("arm_upper_" + side), Lower = source.IndexOf("arm_lower_" + side), End = source.IndexOf("hand_" + side) };
            var goal = center + forward * reach * (side == "L" ? .75f : .5f) + lateral * (side == "L" ? -4 : 4) - up * reach * .1f;
            EffectorIk.ApplyGoals(new() { pose }, source, chain, new[] { goal }, lateral, soften: 0);
        }
        var input = new Pose(pose).ToWorld(source);
        pose[source.IndexOf("support_goal")] = XForm.ToLocal(input[source.IndexOf("hand_R")], input[source.IndexOf("hand_L")]);
        var targets = new Dictionary<string, string> { ["support_goal"] = "hand_L" };
        var rig = new SmartPortRig(source, target, targets, new[] { "grip_L", "grip_R" });
        var result = rig.Transfer(pose);
        var world = new Pose(result).ToWorld(rig.Target);
        var error = Vector3.Distance(world[rig.Target.IndexOf("hand_L")].Pos, world[rig.Target.IndexOf("support_goal")].Pos);
        Assert.True(error < .01f, $"Support hand missed its goal by {error}");
        foreach (var side in new[] { "L", "R" })
        foreach (var name in new[] { "arm_upper_", "arm_lower_", "hand_" })
        {
            var index = rig.Target.IndexOf(name + side);
            Assert.True(Vector3.Distance(rig.Target[index].RestLocal.Pos, result[index].Pos) < .0001f);
        }
        var unfitted = new SmartPortRig(source, target, targets);
        var before = new Pose(unfitted.Transfer(pose)).ToWorld(unfitted.Target);
        Assert.True(Vector3.Distance(before[unfitted.Target.IndexOf("hand_L")].Pos, before[unfitted.Target.IndexOf("support_goal")].Pos) > .1f);
        Assert.All(rig.Transfer(Enumerable.Repeat(XForm.Identity, source.Count).ToArray(), true), delta =>
        {
            Assert.True(delta.Pos.Length() < .0001f);
            Assert.True(MathQ.AngleBetween(delta.Rot, Quaternion.Identity) < .001f);
        });
    }
}