HumanoidMocap/Core/Motion/CaptureSpineSpread.cs

Static utility that redistributes spine rotation across a chain of spine bones on captured frames so the overall hips-to-head rotation is preserved while spreading bend according to a falloff. It finds consecutive spine bones under the hips, computes weighted slerps from rest rotations, and rewrites per-frame local rotations.

using System;
using System.Collections.Generic;
using System.Linq;
using HumanoidMocap.Core.Mapping;
using HumanoidMocap.Core.Maths;
using HumanoidMocap.Core.Target;

namespace HumanoidMocap.Core.Motion;
using Quaternion = System.Numerics.Quaternion;

/// <summary>Spreads a reconstructed body's spine bend along the target's spine. The body network folds a seated
/// or bent-over back almost entirely at its lowest spine joint (a seated tumbler: 52 degrees there, 16 and 17
/// above), which on a character is a crease at the base of the back. For each frame the rotation from the hips
/// to the top of the spine is kept exactly, so the chest, head and arms do not move; only how it is shared
/// between the spine bones changes, each taking <see cref="Falloff"/> of the share of the one below it and the
/// last absorbing any remainder.</summary>
public static class CaptureSpineSpread
{
    public const float Falloff = .6f;

    /// <returns>The number of frames changed.</returns>
    public static int Apply( List<XForm[]> frames, TargetRig target )
    {
        var rig = target.Skeleton;
        if ( target.BoneForRole( BoneRole.Hips ) is not int hips ) return 0;
        var chain = new List<int>(); var parent = hips;
        foreach ( var role in new[] { BoneRole.Spine0, BoneRole.Spine1, BoneRole.Spine2, BoneRole.Spine3, BoneRole.Spine4 } )
        {
            if ( target.BoneForRole( role ) is not int bone ) continue;
            if ( rig[bone].ParentIndex != parent ) break;
            chain.Add( bone ); parent = bone;
        }
        if ( chain.Count < 2 ) return 0;
        var rest = chain.Select( b => rig[b].RestLocal.Rot ).ToArray();
        var raw = Enumerable.Range( 0, chain.Count ).Select( i => (float)Math.Pow( Falloff, i ) ).ToArray(); var sum = raw.Sum();
        var weights = raw.Select( v => v / sum ).ToArray();
        var changed = 0;
        foreach ( var frame in frames )
        {
            var total = Quaternion.Identity; var delta = Quaternion.Identity;
            for ( var i = 0; i < chain.Count; i++ )
            {
                var local = frame[chain[i]].Rot;
                total = Quaternion.Normalize( total * local );
                delta = Quaternion.Normalize( delta * (Quaternion.Inverse( rest[i] ) * local) );
            }
            var sofar = Quaternion.Identity;
            for ( var i = 0; i < chain.Count - 1; i++ )
            {
                var local = Quaternion.Normalize( rest[i] * Quaternion.Slerp( Quaternion.Identity, delta, weights[i] ) );
                frame[chain[i]].Rot = local; sofar = Quaternion.Normalize( sofar * local );
            }
            frame[chain[^1]].Rot = Quaternion.Normalize( Quaternion.Inverse( sofar ) * total );
            changed++;
        }
        return changed;
    }
}