tests/CitizenAnimationSetup/StockBindPoseTests.cs
using System.Numerics;
using HumanoidRetargeter.Mapping;
using HumanoidRetargeter.Maths;
using HumanoidRetargeter.Skeleton;
using HumanoidRetargeter.Target;
using Xunit;
using SkeletonModel = HumanoidRetargeter.Skeleton.Skeleton;

namespace HumanoidRetargeter.Tests.Target;

public class StockBindPoseTests
{
    private const string RigJson = """
    { "name": "stock", "bones": [
        { "name": "pelvis", "parent": null, "class": "Animated", "role": "Hips", "local_pos": [0, 100, 0], "local_rot_xyzw": [0, 0, 0, 1], "tail_world": [0, 110, 0] },
        { "name": "helper", "parent": "pelvis", "class": "ConstraintDriven", "local_pos": [1, 0, 0], "local_rot_xyzw": [0, 0, 0, 1] },
        { "name": "root_IK", "parent": null, "class": "IkBaked", "local_pos": [0, 0, 0], "local_rot_xyzw": [0, 0, 0, 1] }
    ] }
    """;

    [Fact]
    public void RebindingPreservesCuratedHelpersAndAdoptsCompiledRest()
    {
        var stock = TargetRig.Load(RigJson);
        var rotation = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, MathF.PI / 2);
        var definitions = stock.Skeleton.Bones.Select(b => new BoneDefinition(b.Name,
            b.ParentIndex < 0 ? null : stock.Skeleton[b.ParentIndex].Name,
            b.ParentIndex < 0 ? new XForm(Vector3.Transform(b.RestLocal.Pos, rotation), rotation) : b.RestLocal)).ToList();
        definitions.Add(new BoneDefinition("custom_extra", "pelvis", XForm.Identity));
        var bind = SkeletonModel.Create(definitions);
        var rig = stock.WithBindPose(bind);
        Assert.Same(bind, rig.Skeleton);
        Assert.True(rig.HelpersAreConstraintDriven);
        Assert.Equal(BoneClass.ConstraintDriven, rig.ClassOf(bind.IndexOf("helper")));
        Assert.Equal(BoneClass.IkBaked, rig.ClassOf(bind.IndexOf("root_IK")));
        Assert.Equal(BoneClass.Animated, rig.ClassOf(bind.IndexOf("custom_extra")));
        Assert.Equal(bind.IndexOf("pelvis"), rig.BoneForRole(BoneRole.Hips));
        Assert.True(Vector3.Distance(new Vector3(-110, 0, 0), rig.TailWorldOf(bind.IndexOf("pelvis"))!.Value) < .001f);
        Assert.Equal(new Vector3(0, 100, 0), stock.Skeleton.RestWorld[stock.Skeleton.IndexOf("pelvis")].Pos);
    }

    [Fact]
    public void MissingMappedBonesFailRatherThanDroppingMotion()
        => Assert.Throws<ArgumentException>(() => TargetRig.Load(RigJson).WithBindPose(
            SkeletonModel.Create(new[] { new BoneDefinition("unrelated", null, XForm.Identity) })));

    [Fact]
    public void PrefabMeshUsesTheSameDmxRootCorrectionAsAnExplicitMesh()
    {
        var rig = TargetRig.Load(RigJson);
        var frames = new[] { rig.Skeleton.Bones.Select(b => b.RestLocal).ToArray() };
        var prefab = new RetargetTargetSpec { Rig = rig, VmdlScale = .3937f, CompensateDmxRootYaw = true };
        var explicitMesh = new RetargetTargetSpec { Rig = rig, VmdlScale = .3937f, MeshFilePath = "body.fbx" };
        var expected = Retargeter.TestHook_CompensateEmbeddedMeshRootYaw(frames, explicitMesh);
        var actual = Retargeter.TestHook_CompensateEmbeddedMeshRootYaw(frames, prefab);
        Assert.Equal(expected[0], actual[0]);
        Assert.NotEqual(frames[0][0].Rot, actual[0][0].Rot);
        Assert.Equal(rig.Skeleton[0].RestLocal, frames[0][0]);
    }
}