Code/HumanoidRetargeter/Skeleton/Pose.cs

Represents a skeleton pose as an array of parent-relative local XForm transforms and provides helpers. It can wrap an existing XForm[] array, create a rest pose from a Skeleton, and convert locals to world-space transforms via forward kinematics.

using System;
using HumanoidRetargeter.Maths;

namespace HumanoidRetargeter.Skeleton;

/// <summary>
/// A single pose of a skeleton: one parent-relative local transform per bone,
/// indexed exactly like the skeleton's bone list.
/// </summary>
public sealed class Pose
{
    /// <summary>Parent-relative local transforms, one per bone (skeleton bone order).</summary>
    public XForm[] Locals { get; }

    /// <summary>Wraps an existing local-transform array (not copied).</summary>
    public Pose(XForm[] locals)
    {
        Locals = locals ?? throw new ArgumentNullException(nameof(locals));
    }

    /// <summary>Creates a pose initialized to the skeleton's rest local transforms.</summary>
    public static Pose Rest(Skeleton skeleton)
    {
        ArgumentNullException.ThrowIfNull(skeleton);
        var locals = new XForm[skeleton.Count];
        for (var i = 0; i < locals.Length; i++)
            locals[i] = skeleton[i].RestLocal;
        return new Pose(locals);
    }

    /// <summary>
    /// Forward kinematics: composes local transforms down the hierarchy and returns
    /// one world transform per bone.
    /// </summary>
    /// <exception cref="ArgumentException">Thrown when the pose length does not match the skeleton.</exception>
    public XForm[] ToWorld(Skeleton skeleton)
    {
        ArgumentNullException.ThrowIfNull(skeleton);
        if (Locals.Length != skeleton.Count)
            throw new ArgumentException(
                $"Pose has {Locals.Length} bones but skeleton has {skeleton.Count}.", nameof(skeleton));

        var world = new XForm[Locals.Length];
        for (var i = 0; i < Locals.Length; i++)
        {
            var parent = skeleton[i].ParentIndex;
            world[i] = parent < 0 ? Locals[i] : XForm.Compose(world[parent], Locals[i]);
        }
        return world;
    }
}