Defines RigJoint and RigSkeleton types for a generated skeleton. RigJoint stores name, parent index, bind position and optional hinge axis. RigSkeleton holds a list of joints, provides Root and IndexOf helpers, and a Validate method that enforces single root, parent-before-child ordering, unique names, and finite positions.
using System.Numerics;
namespace AutoRig.Rig;
// s&box compat: the engine defines Vector2/Vector3 in the GLOBAL namespace, which
// shadows using-directive imports - alias explicitly to System.Numerics.
using Vector3 = System.Numerics.Vector3;
/// <summary>One joint of a generated skeleton (bind pose, world space).</summary>
public sealed class RigJoint
{
public required string Name { get; set; }
/// <summary>Parent joint index; -1 for the root. Parents always precede children.</summary>
public required int Parent { get; set; }
/// <summary>Bind position in mesh (world) space.</summary>
public required Vector3 Position { get; set; }
/// <summary>Preferred rotation axis (unit) for mechanical joints; Zero = free/ball joint.</summary>
public Vector3 HingeAxis { get; set; }
}
/// <summary>A generated skeleton: joints in parent-before-child order.</summary>
public sealed class RigSkeleton
{
public List<RigJoint> Joints { get; } = new();
/// <summary>Index of the root joint (the single joint with Parent == -1).</summary>
public int Root
{
get
{
for ( var i = 0; i < Joints.Count; i++ )
if ( Joints[i].Parent < 0 )
return i;
return -1;
}
}
/// <summary>Index of the named joint, -1 when absent.</summary>
public int IndexOf( string name )
{
for ( var i = 0; i < Joints.Count; i++ )
if ( string.Equals( Joints[i].Name, name, StringComparison.Ordinal ) )
return i;
return -1;
}
/// <summary>Checks structural invariants.</summary>
/// <exception cref="FormatException">On any malformed skeleton.</exception>
public void Validate()
{
if ( Joints.Count == 0 )
throw new FormatException( "Skeleton has no joints." );
var roots = 0;
var names = new HashSet<string>( StringComparer.Ordinal );
for ( var i = 0; i < Joints.Count; i++ )
{
var joint = Joints[i];
if ( joint.Parent < 0 )
roots++;
else if ( joint.Parent >= i )
throw new FormatException(
$"Joint '{joint.Name}' (index {i}) has parent {joint.Parent}; parents must precede children." );
if ( !names.Add( joint.Name ) )
throw new FormatException( $"Duplicate joint name '{joint.Name}'." );
var p = joint.Position;
if ( !float.IsFinite( p.X ) || !float.IsFinite( p.Y ) || !float.IsFinite( p.Z ) )
throw new FormatException( $"Joint '{joint.Name}' has a non-finite position." );
}
if ( roots != 1 )
throw new FormatException( $"Skeleton must have exactly one root joint (found {roots})." );
}
}