tests/test_ccd_ik.cs
using System.Numerics;
using HumanoidMocapX.Core.Cleanup;
using HumanoidMocapX.Core.Maths;
using HumanoidMocapX.Core.Skeleton;

static class CcdIkTests
{
    internal static void Run()
    {
        var skeleton = Skeleton.Create(new[]
        {
            new BoneDefinition("root", null, XForm.Identity),
            new BoneDefinition("elbow", "root", new XForm(Vector3.UnitX, Quaternion.Identity)),
            new BoneDefinition("hand", "elbow", new XForm(Vector3.UnitY, Quaternion.Identity)),
            new BoneDefinition("finger", "hand", new XForm(Vector3.UnitY * .2f, Quaternion.CreateFromAxisAngle(Vector3.UnitX, .4f)))
        });
        var rest = skeleton.Bones.Select(b => b.RestLocal).ToArray();
        var solver = new CcdIk(skeleton, new[] { 0, 1 }, 2);
        var pose = (XForm[])rest.Clone();
        var residual = solver.Solve(pose, new Vector3(.7f, 1.3f, .4f), iterations: 128, tolerance: 1e-5f);
        MotionTests.Check(residual < 1e-4, $"CCD must reach a reachable target; residual {residual}");
        Invariants(rest, pose);
        pose = (XForm[])rest.Clone();
        residual = solver.Solve(pose, new Vector3(20, 0, 0), iterations: 128);
        MotionTests.Near(18, residual, .001);
        Invariants(rest, pose);

        var locked = (XForm[])rest.Clone();
        var limits = new LockedJoint(rest);
        solver.Solve(locked, new Vector3(-1, 1, 0), constrain: limits.Apply);
        MotionTests.Near(0, MathQ.AngleBetween(rest[0].Rot, locked[0].Rot), 1e-6);
        MotionTests.Check(limits.Calls > 0, "CCD must enforce the supplied joint constraint");

        var offsetSolver = new CcdIk(skeleton, new[] { 0 }, 2);
        var offset = new Vector3(.1f, .3f, 0);
        var offsetGoal = Vector3.Transform(new Vector3(1, 1, 0) + offset,
            Quaternion.CreateFromAxisAngle(Vector3.UnitZ, .4f));
        pose = (XForm[])rest.Clone();
        MotionTests.Check(offsetSolver.Solve(pose, offsetGoal, tipOffset: offset) < .001f, "CCD tip offset must reach the target");

        Reject(() => new CcdIk(skeleton, new[] { 1, 0 }, 2));
        Reject(() => new CcdIk(skeleton, new[] { -1 }, 2));
        Reject(() => solver.Solve(rest, new Vector3(float.NaN, 0, 0)));
        Reject(() => solver.Solve(rest, Vector3.Zero, iterations: 0));
        Reject(() => solver.Solve(rest, Vector3.Zero, gain: float.NaN));
        var invalid = (XForm[])rest.Clone(); invalid[1].Rot = default;
        Reject(() => solver.Solve(invalid, Vector3.Zero));

        pose = (XForm[])rest.Clone();
        for (var i = 0; i < 10; i++) solver.Solve(pose, new Vector3(.7f, 1.3f, .4f));
        var before = GC.GetAllocatedBytesForCurrentThread();
        for (var i = 0; i < 100; i++) solver.Solve(pose, new Vector3(.7f, 1.3f, .4f));
        MotionTests.Check(GC.GetAllocatedBytesForCurrentThread() == before, "CCD solve must not allocate");
        Console.WriteLine("PASS: DigitalRune CCD adaptation, reach residuals, constraints, offsets, validation and zero-allocation solve");
    }

    static void Invariants(XForm[] before, XForm[] after)
    {
        for (var i = 0; i < before.Length; i++) MotionTests.Near(0, Vector3.Distance(before[i].Pos, after[i].Pos));
        for (var i = 2; i < before.Length; i++) MotionTests.Near(0, MathQ.AngleBetween(before[i].Rot, after[i].Rot));
    }

    static void Reject(Action action)
    {
        try { action(); } catch (ArgumentException) { return; }
        throw new Exception("CCD accepted an invalid chain or input");
    }

    sealed class LockedJoint(XForm[] reference)
    {
        internal int Calls;
        internal void Apply(XForm[] pose, int bone) { Calls++; if (bone == 0) pose[bone].Rot = reference[bone].Rot; }
    }
}