Code/HumanoidRetargeter/Core/Trim/ClipTrim.cs

Utility that trims a solved animation clip to a subset of frames and returns the kept frames as XForm arrays. When non-null up vector is provided, it shifts parentless bones (root/pelvis) on the ground plane so that frames separated by a cut preserve lateral continuity; height and rotation are not changed.

#nullable enable annotations

using System;
using System.Collections.Generic;
using System.Numerics;
using HumanoidRetargeter.Core.Maths;
using SkeletonModel = HumanoidRetargeter.Core.Skeleton.Skeleton;

namespace HumanoidRetargeter.Core.Trim;
using Vector3 = System.Numerics.Vector3; // s&box compat: shadow engine's global-namespace Vector3 (see Code/HumanoidRetargeter/Assembly.cs)

/// <summary>Cuts a solved clip down to a list of kept frames.</summary>
public static class ClipTrim
{
    /// <summary>
    /// The frames at <paramref name="kept"/> (ascending, no repeats), in order. Where a section was cut
    /// out, the parentless bones (pelvis or root, which carry the travel) are shifted on the ground plane
    /// so the frame after the cut continues one step on from the frame before it: cutting the middle of a
    /// walk must not teleport the character. Height and rotation are untouched. Null
    /// <paramref name="up"/> skips the shift.
    /// </summary>
    public static List<XForm[]> Apply(IReadOnlyList<XForm[]> frames, IReadOnlyList<int> kept, SkeletonModel skeleton, Vector3? up)
    {
        if (kept.Count == 0)
            throw new ArgumentException("A trim must keep at least one frame.", nameof(kept));
        for (var i = 0; i < kept.Count; i++)
        {
            if (kept[i] < 0 || kept[i] >= frames.Count || (i > 0 && kept[i] <= kept[i - 1]))
                throw new ArgumentException($"Trim frame {kept[i]} is out of order or outside the clip's {frames.Count} frames.", nameof(kept));
        }

        var shift = new Vector3[skeleton.Count];
        var result = new List<XForm[]>(kept.Count);
        for (var i = 0; i < kept.Count; i++)
        {
            var source = frames[kept[i]];
            if (i > 0 && kept[i] != kept[i - 1] + 1 && up is { } u)
            {
                var before = kept[i - 1];
                for (var b = 0; b < skeleton.Count; b++)
                {
                    if (skeleton[b].ParentIndex >= 0)
                        continue;
                    var step = before > 0 ? frames[before][b].Pos - frames[before - 1][b].Pos : Vector3.Zero;
                    var offset = result[i - 1][b].Pos + step - source[b].Pos;
                    shift[b] = offset - Vector3.Dot(offset, u) * u;
                }
            }

            var frame = new XForm[source.Length];
            for (var b = 0; b < frame.Length; b++)
                frame[b] = new XForm(source[b].Pos + shift[b], source[b].Rot);
            result.Add(frame);
        }
        return result;
    }
}