Generator for a target-rig JSON used by the HumanoidRetargeter. It loads a source rig (via RigJson.Load), iterates bones and geometry, classifies bones, emits a deterministic JSON representation including name, description, units, quaternion order, bones array with parent, class, optional role, local position/rotation and head/tail world vectors.
using System;
using System.IO;
using System.Numerics;
using System.Text;
using System.Text.Json;
using HumanoidRetargeter.Skeleton;
namespace HumanoidRetargeter.Target;
using Vector3 = System.Numerics.Vector3; // s&box compat: shadow engine's global-namespace Vector3 (see Code/HumanoidRetargeter/Assembly.cs)
/// <summary>
/// Generates the committed s&box target-rig definition
/// (<c>Assets/humanoid_retargeter/target_rig_sbox.json</c>) from the research ground-truth
/// rig JSON (<c>research/rig_human_male.json</c>). Pure string → string; the generator test
/// owns reading/writing the files (regenerate-and-diff pattern).
/// </summary>
public static class TargetRigGenerator
{
/// <summary>Rig name written into the default (5-finger human male) target rig.</summary>
public const string HumanMaleName = "sbox_human_male";
/// <summary>Description written into the default (5-finger human male) target rig.</summary>
public const string HumanMaleDescription =
"s&box humanoid target rig. Generated by TargetRigGenerator from research/rig_human_male.json - do not hand-edit.";
/// <summary>Rig name written into the classic (4-finger) citizen target rig.</summary>
public const string CitizenName = "sbox_citizen";
/// <summary>Description written into the classic (4-finger) citizen target rig.</summary>
public const string CitizenDescription =
"s&box classic citizen target rig (4 fingers, no pinky). Generated by TargetRigGenerator from dev/HumanoidRetargeter.Tests/fixtures/rig_citizen.json - do not hand-edit.";
/// <summary>
/// Deterministically produces the target-rig JSON: every bone of the source rig in
/// topological order with its rest transforms (centimeters), bone class, and — for
/// <see cref="BoneClass.Animated"/> bones only — its canonical role. The defaults of
/// <paramref name="name"/>/<paramref name="description"/> produce the shipped human male
/// rig; the classic citizen rig passes <see cref="CitizenName"/>/<see cref="CitizenDescription"/>.
/// </summary>
public static string Generate(string rigJson,
string name = HumanMaleName, string description = HumanMaleDescription)
{
ArgumentNullException.ThrowIfNull(rigJson);
var (skeleton, geometry) = RigJson.Load(rigJson);
using var buffer = new MemoryStream();
using (var writer = new Utf8JsonWriter(buffer, new JsonWriterOptions { Indented = true }))
{
writer.WriteStartObject();
writer.WriteString("name", name);
writer.WriteString("description", description);
writer.WriteString("units", "cm");
writer.WriteString("quaternion_order", "xyzw");
writer.WriteStartArray("bones");
foreach (var bone in skeleton.Bones)
{
writer.WriteStartObject();
writer.WriteString("name", bone.Name);
if (bone.ParentIndex < 0)
writer.WriteNull("parent");
else
writer.WriteString("parent", skeleton[bone.ParentIndex].Name);
var boneClass = SboxBoneClassifier.Classify(bone.Name);
writer.WriteString("class", boneClass.ToString());
if (boneClass == BoneClass.Animated)
{
var role = SboxBoneClassifier.RoleFor(bone.Name)
?? throw new InvalidOperationException(
$"Animated s&box bone '{bone.Name}' has no role assignment; extend SboxBoneClassifier.");
writer.WriteString("role", role.ToString());
}
WriteVector(writer, "local_pos", bone.RestLocal.Pos);
WriteQuaternion(writer, "local_rot_xyzw", bone.RestLocal.Rot);
var (head, tail) = geometry[bone.Name];
WriteVector(writer, "head_world", head);
WriteVector(writer, "tail_world", tail);
writer.WriteEndObject();
}
writer.WriteEndArray();
writer.WriteEndObject();
}
return Encoding.UTF8.GetString(buffer.ToArray()) + "\n"; // Environment.NewLine is not s&box-whitelisted
}
private static void WriteVector(Utf8JsonWriter writer, string name, Vector3 v)
{
writer.WriteStartArray(name);
writer.WriteNumberValue(v.X);
writer.WriteNumberValue(v.Y);
writer.WriteNumberValue(v.Z);
writer.WriteEndArray();
}
private static void WriteQuaternion(Utf8JsonWriter writer, string name, Quaternion q)
{
writer.WriteStartArray(name);
writer.WriteNumberValue(q.X);
writer.WriteNumberValue(q.Y);
writer.WriteNumberValue(q.Z);
writer.WriteNumberValue(q.W);
writer.WriteEndArray();
}
}