Editor/Inference/SamCompactPose.cs
// C# adaptation of SAM 3D Body's MHR parameter conversion. See LICENSE-SAM and THIRD_PARTY_NOTICES.md.
// Copyright (c) Meta Platforms, Inc. and affiliates.
using System;

namespace HumanoidMocapX.Inference;

/// <summary>Converts SAM's continuous body/hand predictions to MHR parameter order.</summary>
internal static class SamCompactPose
{
    internal const int BodyContinuous = 260, BodyParameters = 133, HandContinuous = 54, HandParameters = 27;
    static readonly int[] Triples = [0, 2, 4, 6, 8, 10, 12, 13, 14, 15, 16, 17, 18, 19, 20,
        21, 22, 23, 24, 25, 26, 27, 28, 29, 34, 35, 36, 37, 38, 39, 44, 45, 46, 53, 54, 55,
        64, 65, 66, 85, 69, 73, 86, 70, 79, 87, 71, 82, 88, 72, 76, 91, 92, 93, 112, 96, 100,
        113, 97, 106, 114, 98, 109, 115, 99, 103, 130, 131, 132];
    static readonly int[] Singles = [1, 3, 5, 7, 9, 11, 30, 31, 32, 33, 40, 41, 42, 43, 47, 48, 49, 50, 51, 52,
        56, 57, 58, 59, 60, 61, 62, 63, 67, 68, 74, 75, 77, 78, 80, 81, 83, 84, 89, 90,
        94, 95, 101, 102, 104, 105, 107, 108, 110, 111, 116, 117, 118, 119, 120, 121, 122, 123];
    static readonly int[] HandDofs = [3, 1, 1, 3, 1, 1, 3, 1, 1, 3, 1, 1, 2, 3, 1, 1];

    /// <summary>Writes 133 parameters, including the hand and jaw slots that the
    /// head masks later. Rejects malformed, non-finite or overlapping buffers.</summary>
    internal static void Body(ReadOnlySpan<float> continuous, Span<float> output)
    {
        Validate(continuous, output, BodyContinuous, BodyParameters);
        Span<float> angles = stackalloc float[3];
        var triples = Triples; var singles = Singles;
        for (var i = 0; i < triples.Length; i += 3)
        {
            SamRotation.CompactXYZ(continuous.Slice(i * 2, 6), angles);
            for (var axis = 0; axis < 3; axis++) output[triples[i + axis]] = angles[axis];
        }
        var start = triples.Length * 2;
        for (var i = 0; i < singles.Length; i++) output[singles[i]] = MathF.Atan2(continuous[start + i * 2], continuous[start + i * 2 + 1]);
        continuous.Slice(BodyContinuous - 6).CopyTo(output.Slice(124, 6));
    }

    /// <summary>Writes 27 hand parameters in joint/axis order. Two-axis joints use
    /// two sine/cosine pairs; three-axis joints use two rotation-matrix columns.</summary>
    internal static void Hand(ReadOnlySpan<float> continuous, Span<float> output)
    {
        Validate(continuous, output, HandContinuous, HandParameters);
        var at = 0;
        foreach (var count in HandDofs)
        {
            if (count == 3) SamRotation.CompactXYZ(continuous.Slice(at * 2, 6), output.Slice(at, 3));
            else for (var axis = 0; axis < count; axis++) output[at + axis] = MathF.Atan2(continuous[(at + axis) * 2], continuous[(at + axis) * 2 + 1]);
            at += count;
        }
    }

    static void Validate(ReadOnlySpan<float> input, Span<float> output, int inputs, int outputs)
    {
        if (input.Length != inputs || output.Length != outputs || input.Overlaps(output))
            throw new ArgumentException("SAM compact pose dimensions are invalid or overlap.");
        foreach (var value in input) if (!float.IsFinite(value)) throw new ArgumentException("SAM compact pose must be finite.");
    }
}