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.
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." );
}
}
}