Code/AutoRig/Rig/SkinWeights.cs

Represents per-vertex skinning data for a rig, storing up to four bone indices and four weights per vertex. Provides a factory for rigid binding and a Validate method that checks array lengths, weight finiteness/non-negativity, bone index ranges, and that each vertex weights sum to 1.

File Access
using AutoRig.Mesh;

namespace AutoRig.Rig;

/// <summary>
/// Per-vertex skinning: up to 4 bone influences per vertex, parallel to mesh positions.
/// </summary>
public sealed class SkinWeights
{
    /// <summary>4 bone indices per vertex (unused slots 0 with weight 0).</summary>
    public required int[] BoneIndices { get; init; }

    /// <summary>4 weights per vertex; each vertex's weights sum to 1.</summary>
    public required float[] Weights { get; init; }

    /// <summary>Rigid binding: each vertex fully weighted to one bone.</summary>
    public static SkinWeights Rigid( int vertexCount, int[] vertexBone )
    {
        ArgumentNullException.ThrowIfNull( vertexBone );
        if ( vertexBone.Length != vertexCount )
            throw new FormatException(
                $"vertexBone length {vertexBone.Length} does not match vertex count {vertexCount}." );

        var indices = new int[vertexCount * 4];
        var weights = new float[vertexCount * 4];
        for ( var v = 0; v < vertexCount; v++ )
        {
            indices[v * 4] = vertexBone[v];
            weights[v * 4] = 1f;
        }
        return new SkinWeights { BoneIndices = indices, Weights = weights };
    }

    /// <summary>Checks structural invariants against the mesh and skeleton.</summary>
    /// <exception cref="FormatException">On any malformed weighting.</exception>
    public void Validate( RigMesh mesh, RigSkeleton skeleton )
    {
        ArgumentNullException.ThrowIfNull( mesh );
        ArgumentNullException.ThrowIfNull( skeleton );

        var vertexCount = mesh.Positions.Length;
        if ( BoneIndices.Length != vertexCount * 4 || Weights.Length != vertexCount * 4 )
            throw new FormatException(
                $"Skin arrays ({BoneIndices.Length}/{Weights.Length}) do not match 4 influences x {vertexCount} vertices." );

        for ( var v = 0; v < vertexCount; v++ )
        {
            float total = 0;
            for ( var k = 0; k < 4; k++ )
            {
                var weight = Weights[v * 4 + k];
                if ( weight < 0f || !float.IsFinite( weight ) )
                    throw new FormatException( $"Vertex {v} has a negative or non-finite weight." );
                if ( weight > 0f )
                {
                    var bone = BoneIndices[v * 4 + k];
                    if ( bone < 0 || bone >= skeleton.Joints.Count )
                        throw new FormatException(
                            $"Vertex {v} references bone {bone} (skeleton has {skeleton.Joints.Count})." );
                }
                total += weight;
            }
            if ( MathF.Abs( total - 1f ) > 1e-3f )
                throw new FormatException( $"Vertex {v} weights sum to {total:0.####}, expected 1." );
        }
    }
}