Editor/Inference/MhrSkeleton.cs
using System;
using System.IO;
using System.Threading;
namespace HumanoidMocapX.Inference;
/// <summary>Maps MHR model parameters to joint parameters and global states in
/// the checkpoint's units. Reuses working storage; one instance per inference session.</summary>
internal sealed class MhrSkeleton
{
internal const string Prefix = "character_torch.";
internal const int JointParameters = 7;
readonly Tensor mapping, offsets, prerotations;
readonly int[] parents;
readonly MhrState[] states;
internal int Joints => parents.Length;
internal int Parameters => mapping.Cols;
internal MhrSkeleton(FlowWeights weights, ReadOnlySpan<int> parents, int parameters)
{
ArgumentNullException.ThrowIfNull(weights);
if (parents.Length == 0 || parameters < 1) throw new ArgumentException("MHR skeleton dimensions must be positive.");
mapping = weights.Get(Prefix + "parameter_transform.parameter_transform", parameters, checked(parents.Length * JointParameters));
offsets = weights.Get(Prefix + "skeleton.joint_translation_offsets", 3, parents.Length);
prerotations = weights.Get(Prefix + "skeleton.joint_prerotations", 4, parents.Length);
for (var joint = 0; joint < parents.Length; joint++)
{
if (parents[joint] < -1 || parents[joint] >= joint)
throw new InvalidDataException("MHR parents must precede their children, with -1 for roots.");
var q = prerotations.Row(joint); double squaredNorm = 0;
foreach (var value in q) squaredNorm += (double)value * value;
if (Math.Abs(squaredNorm - 1) > 1e-4) throw new InvalidDataException("MHR prerotation must be a unit quaternion.");
}
this.parents = parents.ToArray(); states = new MhrState[parents.Length];
}
/// <summary>Writes seven local joint parameters and eight global-state values
/// (translation, XYZW quaternion, scale) per joint. Rejects non-finite or
/// overlapping buffers and invalid dimensions; cancellation may leave partial output.</summary>
internal void Run(ReadOnlySpan<float> modelParameters, Span<float> jointParameters, Span<float> globalStates,
CancellationToken cancellation = default)
{
cancellation.ThrowIfCancellationRequested();
if (modelParameters.Length != Parameters || jointParameters.Length != Joints * JointParameters || globalStates.Length != Joints * MhrState.Elements)
throw new ArgumentException("MHR parameter or skeleton-state dimensions do not match the model.");
if (modelParameters.Overlaps(jointParameters) || modelParameters.Overlaps(globalStates) || jointParameters.Overlaps(globalStates)
|| AliasesWeights(jointParameters) || AliasesWeights(globalStates))
throw new ArgumentException("MHR outputs must not overlap inputs, each other or model weights.");
foreach (var value in modelParameters)
if (!float.IsFinite(value)) throw new ArgumentException("MHR model parameters must be finite.", nameof(modelParameters));
FlowLinear.Run(modelParameters, 1, mapping, null, jointParameters, cancellation);
for (var joint = 0; joint < Joints; joint++)
{
cancellation.ThrowIfCancellationRequested();
var local = MhrState.Local(jointParameters.Slice(joint * JointParameters, JointParameters), offsets.Row(joint), prerotations.Row(joint));
states[joint] = parents[joint] < 0 ? local : states[parents[joint]].Then(local);
var output = globalStates.Slice(joint * MhrState.Elements, MhrState.Elements);
states[joint].Write(output);
foreach (var value in output)
if (!float.IsFinite(value)) throw new ArithmeticException("MHR skeleton calculation produced a non-finite state.");
if (output[7] <= 0) throw new ArithmeticException("MHR skeleton scale underflowed.");
}
}
bool AliasesWeights(Span<float> output)
=> output.Overlaps(mapping.Data) || output.Overlaps(offsets.Data) || output.Overlaps(prerotations.Data);
}