Editor/Inference/MhrBlendShapes.cs
using System;
using System.Threading;

namespace HumanoidMocapX.Inference;

/// <summary>Applies MHR identity and facial blendshapes to the base mesh, in model
/// units. Owns reusable working storage; one instance per inference session.</summary>
internal sealed class MhrBlendShapes
{
    readonly Tensor identity, expression, basis;
    readonly float[] face;
    internal int Vertices { get; }
    internal int Identities => identity.Cols;
    internal int Expressions => expression.Cols;

    internal MhrBlendShapes(FlowWeights weights, int vertices, int identities, int expressions)
    {
        ArgumentNullException.ThrowIfNull(weights);
        if (vertices < 1 || identities < 1 || expressions < 1) throw new ArgumentException("MHR blendshape dimensions must be positive.");
        Vertices = vertices;
        var coordinates = checked(vertices * 3);
        var source = weights.Get(MhrSkeleton.Prefix + "blend_shape.base_shape", 3, vertices);
        basis = new(source.Name, source.Data, new[] { coordinates });
        identity = Transposed(weights.Get(MhrSkeleton.Prefix + "blend_shape.shape_vectors", 3, vertices, identities), identities, coordinates);
        expression = Transposed(weights.Get("face_expressions_model.shape_vectors", 3, vertices, expressions), expressions, coordinates);
        face = new float[coordinates];
    }

    /// <summary>Writes the full unposed mesh. Rejects invalid dimensions, non-finite
    /// coefficients and overlapping input/output. Cancellation may leave partial output.</summary>
    internal void Run(ReadOnlySpan<float> identityCoefficients, ReadOnlySpan<float> expressionCoefficients,
        Span<float> output, CancellationToken cancellation = default)
    {
        cancellation.ThrowIfCancellationRequested();
        if (identityCoefficients.Length != Identities || expressionCoefficients.Length != Expressions || output.Length != face.Length)
            throw new ArgumentException("MHR blendshape input/output dimensions do not match the model.");
        if (identityCoefficients.Overlaps(output) || expressionCoefficients.Overlaps(output) || output.Overlaps(basis.Data))
            throw new ArgumentException("MHR blendshape output must not overlap inputs or model weights.");
        foreach (var value in identityCoefficients)
            if (!float.IsFinite(value)) throw new ArgumentException("MHR identity coefficients must be finite.", nameof(identityCoefficients));
        foreach (var value in expressionCoefficients)
            if (!float.IsFinite(value)) throw new ArgumentException("MHR expression coefficients must be finite.", nameof(expressionCoefficients));
        FlowLinear.Run(identityCoefficients, 1, identity, basis, output, cancellation);
        FlowLinear.Run(expressionCoefficients, 1, expression, null, face, cancellation);
        NeuralOps.AddInPlace(output, face);
        foreach (var value in output)
            if (!float.IsFinite(value)) throw new ArithmeticException("MHR blendshapes produced a non-finite vertex.");
    }

    static Tensor Transposed(Tensor source, int coefficients, int coordinates)
        => new(source.Name, NeuralOps.Transpose(source.Data, coefficients, coordinates), new[] { coefficients, coordinates });
}