Editor/FlowMotionAdapter.cs
using System;
using System.Linq;
using System.Numerics;
using HumanoidMocapX.Core.Mapping;
using HumanoidMocapX.Core.Motion;

namespace HumanoidMocapX;

using Vector3 = System.Numerics.Vector3;

/// <summary>Editor integration with Humanoid Mocap's shared motion format.</summary>
public static class FlowMotionAdapter
{
    static readonly BoneRole[] Roles = {
        BoneRole.Hips, BoneRole.UpperLegL, BoneRole.UpperLegR, BoneRole.Spine0,
        BoneRole.LowerLegL, BoneRole.LowerLegR, BoneRole.Spine1, BoneRole.FootL,
        BoneRole.FootR, BoneRole.Spine2, BoneRole.ToeL, BoneRole.ToeR,
        BoneRole.Neck, BoneRole.ClavicleL, BoneRole.ClavicleR, BoneRole.Head,
        BoneRole.UpperArmL, BoneRole.UpperArmR, BoneRole.LowerArmL, BoneRole.LowerArmR,
        BoneRole.HandL, BoneRole.HandR,
        BoneRole.IndexProxL, BoneRole.IndexMidL, BoneRole.IndexDistL,
        BoneRole.MiddleProxL, BoneRole.MiddleMidL, BoneRole.MiddleDistL,
        BoneRole.PinkyProxL, BoneRole.PinkyMidL, BoneRole.PinkyDistL,
        BoneRole.RingProxL, BoneRole.RingMidL, BoneRole.RingDistL,
        BoneRole.ThumbProxL, BoneRole.ThumbMidL, BoneRole.ThumbDistL,
        BoneRole.IndexProxR, BoneRole.IndexMidR, BoneRole.IndexDistR,
        BoneRole.MiddleProxR, BoneRole.MiddleMidR, BoneRole.MiddleDistR,
        BoneRole.PinkyProxR, BoneRole.PinkyMidR, BoneRole.PinkyDistR,
        BoneRole.RingProxR, BoneRole.RingMidR, BoneRole.RingDistR,
        BoneRole.ThumbProxR, BoneRole.ThumbMidR, BoneRole.ThumbDistR
    };
    static readonly int[] ContactJoints = { 7, 10, 8, 11 };

    /// <summary>Converts local axis-angle rotations and world root travel to the shared
    /// motion format. Rejects invalid shapes, non-finite values and unsupported skeletons.</summary>
    public static MotionDocument ToDocument(this FlowMotion motion)
    {
        ArgumentNullException.ThrowIfNull(motion); motion.Validate();
        var document = new MotionDocument
        {
            Name = motion.Name,
            Backend = "FlowHMR",
            ModelVersion = motion.Model == FlowModel.Base ? "flowhmr_base" : "flowhmr_latest",
            SourceVideo = motion.Video,
            SourceFps = motion.Fps,
            Space = MotionSpace.WorldRelative
        };
        var ground = ContactJoints.Min(j => motion.Rest[j][1]);
        for (var j = 0; j < FlowMotion.JointCount; j++)
        {
            var offset = j == 0 ? MotionDocument.V(motion.Rest[j]) - Vector3.UnitY * ground
                : MotionDocument.V(motion.Rest[j]) - MotionDocument.V(motion.Rest[motion.Parents[j]]);
            document.Bones.Add(new MotionBone
            {
                Name = Roles[j].ToString(),
                Role = Roles[j],
                Parent = motion.Parents[j],
                RestPosition = MotionDocument.A(offset),
                Group = j >= 22 ? "fingers" : "body"
            });
        }
        for (var f = 0; f < motion.Poses.Length; f++) document.Frames.Add(Frame(motion, document, f));
        for (var c = 0; c < ContactJoints.Length && motion.Contacts.Length > 0; c++)
        {
            var probabilities = new float[motion.Poses.Length];
            for (var f = 0; f < probabilities.Length; f++) probabilities[f] = motion.Contacts[f][c];
            document.StationaryJoints.Add(new StationaryJointTrack
            {
                Bone = Roles[ContactJoints[c]].ToString(),
                Source = "FlowHMR predicted foot contacts",
                Probability = probabilities
            });
        }
        document.Diagnostics.Add("World motion, body shape and metric scale are model estimates, not calibrated measurements.");
        document.Diagnostics.Add(motion.Contacts.Length == 0 ? "No foot-contact predictions were returned; foot locking is unavailable."
            : "Foot locking uses predicted left ankle, left toe, right ankle and right toe contacts on the final target rig.");
        document.Validate();
        return document;
    }

    static MotionFrame Frame(FlowMotion motion, MotionDocument document, int f)
    {
        var frame = new MotionFrame
        {
            Time = motion.StartTime + f / motion.Fps,
            Positions = new float[FlowMotion.JointCount][],
            Rotations = new float[FlowMotion.JointCount][],
            Evidence = new JointEvidence[FlowMotion.JointCount]
        };
        for (var j = 0; j < FlowMotion.JointCount; j++)
        {
            // SMPL translation is added to the shaped pelvis; it is not the pelvis position itself.
            frame.Positions[j] = j == 0 ? MotionDocument.A(MotionDocument.V(motion.Rest[0]) + MotionDocument.V(motion.Translations[f]))
                : document.Bones[j].RestPosition.ToArray();
            var rotation = new Vector3(motion.Poses[f][j * 3], motion.Poses[f][j * 3 + 1], motion.Poses[f][j * 3 + 2]);
            var angle = rotation.Length();
            frame.Rotations[j] = MotionDocument.A(angle < 1e-8f ? Quaternion.Identity
                : Quaternion.CreateFromAxisAngle(rotation / angle, angle));
            frame.Evidence[j] = JointEvidence.Reconstructed;
        }
        return frame;
    }

}