tests/test_mhr_skinning.cs
using System.Numerics;
using HumanoidMocapX.Inference;
using HumanoidMocapX.EditorTools;

static class MhrSkinningTests
{
    internal static void Run()
    {
        var bind = States(.2f); var states = States(.7f);
        var joints = new[] { 0, 1, 1, 0, 0 }; var vertices = new[] { 0, 0, 2, 2, 2 }; var weights = new[] { .25f, .75f, .2f, 1.1f, .2f };
        var rest = new float[] { 1, -2, 3, 4, 5, 6, -2, 1, 3 }; var output = new float[rest.Length];
        var model = new MhrSkinning(bind, joints, vertices, weights, 3);
        for (var pass = 0; pass < 2; pass++)
        {
            if (pass > 0) Array.Reverse(rest);
            model.Run(states, rest, output);
            var expected = Reference(bind, states, joints, vertices, weights, rest);
            for (var i = 0; i < output.Length; i++) MotionTests.Near(expected[i], output[i], 3e-6);
            MotionTests.Check(output.AsSpan(3, 3).SequenceEqual(new float[3]), "Unweighted vertex must remain zero");
        }
        for (var warm = 0; warm < 4; warm++) model.Run(states, rest, output);
        var before = GC.GetAllocatedBytesForCurrentThread(); model.Run(states, rest, output);
        MotionTests.Equal(0L, GC.GetAllocatedBytesForCurrentThread() - before);
        MotionTests.Throws<ArgumentException>(() => model.Run(states, rest, rest));
        MotionTests.Throws<ArgumentException>(() => model.Run(states, rest, states.AsSpan(0, rest.Length)));
        MotionTests.Throws<ArgumentException>(() => model.Run(states.AsSpan(1), rest, output));
        MotionTests.Throws<ArgumentException>(() => model.Run(states, rest.AsSpan(1), output));
        MotionTests.Throws<ArgumentException>(() => model.Run(states, rest, output.AsSpan(1)));
        rest[0] = float.NaN; MotionTests.Throws<ArgumentException>(() => model.Run(states, rest, output)); rest[0] = 1;
        foreach (var invalid in new[] { 0f, -1f, float.NaN, float.PositiveInfinity })
        {
            var bad = states.ToArray(); bad[7] = invalid;
            MotionTests.Throws<ArgumentException>(() => model.Run(bad, rest, output));
        }
        var badRotation = states.ToArray(); Array.Clear(badRotation, 3, 4);
        MotionTests.Throws<ArgumentException>(() => model.Run(badRotation, rest, output));
        using var cancelled = new CancellationTokenSource(); cancelled.Cancel();
        MotionTests.Throws<OperationCanceledException>(() => model.Run(states, rest, output, cancelled.Token));
        foreach (var invalid in new[] { -1, 2, int.MaxValue })
        {
            var bad = joints.ToArray(); bad[0] = invalid;
            MotionTests.Throws<InvalidDataException>(() => new MhrSkinning(bind, bad, vertices, weights, 3));
        }
        foreach (var invalid in new[] { -1, 3, int.MaxValue })
        {
            var bad = vertices.ToArray(); bad[0] = invalid;
            MotionTests.Throws<InvalidDataException>(() => new MhrSkinning(bind, joints, bad, weights, 3));
        }
        foreach (var invalid in new[] { -.1f, float.NaN, float.PositiveInfinity })
        {
            var bad = weights.ToArray(); bad[0] = invalid;
            MotionTests.Throws<InvalidDataException>(() => new MhrSkinning(bind, joints, vertices, bad, 3));
        }
        MotionTests.Throws<InvalidDataException>(() => new MhrSkinning(bind.AsSpan(1), joints, vertices, weights, 3));
        MotionTests.Throws<InvalidDataException>(() => new MhrSkinning(bind, joints, vertices, weights.AsSpan(1), 3));
        MotionTests.Throws<ArgumentException>(() => new MhrSkinning(badRotation, joints, vertices, weights, 3));
        Console.WriteLine("PASS: MHR weighted mesh skinning, inverse bind composition, repeated influences, caller buffers, validation and allocation-free reuse");
    }

    internal static void Released(string path)
    {
        var skeleton = MhrCheckpoint.LoadSkeleton(path); var skinning = MhrCheckpoint.LoadSkinning(path);
        using var source = TorchCheckpoint.OpenScriptData(path, "__torch__.MHRDemo");
        const string prefix = MhrSkeleton.Prefix + "linear_blend_skinning.";
        var bind = source.ReadFloat(prefix + "inverse_bind_pose");
        var joints = source.ReadInt64(prefix + "skin_indices_flattened").Select(v => checked((int)v)).ToArray();
        var vertices = source.ReadInt64(prefix + "vert_indices_flattened").Select(v => checked((int)v)).ToArray();
        var weights = source.ReadFloat(prefix + "skin_weights_flattened");
        var rest = source.ReadFloat(MhrSkeleton.Prefix + "mesh.rest_vertices"); var output = new float[rest.Length];
        var input = new float[skeleton.Parameters]; var pose = new float[skeleton.Joints * 7]; var states = new float[skeleton.Joints * 8];
        double maximum = 0;
        for (var pass = 0; pass < 2; pass++)
        {
            if (pass > 0) for (var i = 0; i < input.Length; i++) input[i] = .02f * MathF.Sin(i * .37f);
            skeleton.Run(input, pose, states); skinning.Run(states, rest, output);
            var expected = Reference(bind, states, joints, vertices, weights, rest);
            for (var i = 0; i < output.Length; i++)
            {
                MotionTests.Near(expected[i], output[i], 1e-4); maximum = Math.Max(maximum, Math.Abs(expected[i] - output[i]));
                if (pass == 0) MotionTests.Near(rest[i], output[i], 1e-4);
            }
        }
        using var cancelled = new CancellationTokenSource(); cancelled.Cancel();
        MotionTests.Throws<OperationCanceledException>(() => MhrCheckpoint.LoadSkinning(path, cancelled.Token));
        Console.WriteLine($"PASS: released MHR {skinning.Vertices} vertices/{weights.Length} influences, rest mesh and posed matrix reference; max absolute difference {maximum:G6} model units");
    }

    static float[] States(float angle)
    {
        var output = new float[16];
        for (var joint = 0; joint < 2; joint++)
        {
            var q = Quaternion.CreateFromAxisAngle(Vector3.Normalize(new Vector3(1, -2, 3)), angle + joint * .31f);
            var i = joint * 8; output[i] = joint + .3f; output[i + 1] = angle; output[i + 2] = -.8f;
            output[i + 3] = q.X; output[i + 4] = q.Y; output[i + 5] = q.Z; output[i + 6] = q.W; output[i + 7] = 1 + angle;
        }
        return output;
    }

    static double[] Reference(float[] bind, float[] states, int[] joints, int[] vertices, float[] weights, float[] rest)
    {
        var matrices = new double[states.Length / 8][,]; var output = new double[rest.Length];
        for (var joint = 0; joint < matrices.Length; joint++)
            matrices[joint] = MhrSkeletonTests.Multiply(MhrSkeletonTests.StateMatrix(states.AsSpan(joint * 8, 8)), MhrSkeletonTests.StateMatrix(bind.AsSpan(joint * 8, 8)));
        for (var influence = 0; influence < joints.Length; influence++)
        {
            var m = matrices[joints[influence]]; var vertex = vertices[influence] * 3;
            for (var axis = 0; axis < 3; axis++)
                output[vertex + axis] += weights[influence] * (m[axis, 0] * rest[vertex] + m[axis, 1] * rest[vertex + 1] + m[axis, 2] * rest[vertex + 2] + m[axis, 3]);
        }
        return output;
    }
}