Builds a generic RigSkeleton from a curve SkeletonGraph by creating one RigJoint per graph node. It BFS-traverses from the core, assigns a root joint, groups nodes into branch chains when leaving the core or at junctions, names joints like "limb{branch}_{link:00}", sets parent indices and positions, then validates and returns the skeleton.
using AutoRig.Rig;
namespace AutoRig.Solve.Organic;
/// <summary>
/// Builds a generic rig straight from the curve skeleton: one joint per graph node,
/// rooted at the core, chains named per branch ("limb1_01", …). Used for organic
/// categories without a dedicated template.
/// </summary>
public static class GraphSkeletonBuilder
{
public static RigSkeleton Build( SkeletonGraph graph )
{
ArgumentNullException.ThrowIfNull( graph );
var skeleton = new RigSkeleton();
if ( graph.Nodes.Count == 0 )
throw new FormatException( "Curve skeleton has no nodes." );
var jointOf = new int[graph.Nodes.Count];
Array.Fill( jointOf, -1 );
// BFS from core so parents precede children.
var queue = new Queue<int>();
queue.Enqueue( graph.Core );
jointOf[graph.Core] = 0;
skeleton.Joints.Add( new RigJoint
{
Name = "root",
Parent = -1,
Position = graph.Nodes[graph.Core].Position,
} );
var branchCounter = 0;
var branchOf = new int[graph.Nodes.Count]; // branch id per node
var linkOf = new int[graph.Nodes.Count]; // index within its branch chain
branchOf[graph.Core] = -1;
while ( queue.Count > 0 )
{
var current = queue.Dequeue();
foreach ( var next in graph.Nodes[current].Neighbors.OrderBy( n => n ) )
{
if ( jointOf[next] >= 0 )
continue;
// New branch starts when leaving the core or a junction.
int branch, link;
if ( current == graph.Core || graph.Nodes[current].Neighbors.Count >= 3 )
{
branch = ++branchCounter;
link = 1;
}
else
{
branch = branchOf[current];
link = linkOf[current] + 1;
}
branchOf[next] = branch;
linkOf[next] = link;
jointOf[next] = skeleton.Joints.Count;
skeleton.Joints.Add( new RigJoint
{
Name = $"limb{branch}_{link:00}",
Parent = jointOf[current],
Position = graph.Nodes[next].Position,
} );
queue.Enqueue( next );
}
}
skeleton.Validate();
return skeleton;
}
}