Effigy.Tests/RigTrackNameTests.cs
using Marionette;
namespace Effigy.Tests;
/// <summary>
/// How a track names the thing it drives, now that a clip can animate several objects at once.
///
/// WHY THIS IS WORTH A TEST FILE. Every .riganim ever saved names the main model's bones bare -
/// "hand_R", not "/hand_R" - and playback matches tracks to bones BY NAME. Get the split wrong in
/// either direction and an existing clip animates nothing: no error, no missing asset, just a
/// model standing in its bind pose. That is the worst shape a regression can take, so the rule
/// lives somewhere a test can reach it.
/// </summary>
public static class RigTrackNameTests
{
public static void Run()
{
Report.Section( "rig track names: an old clip's bare bone names still mean the main model" );
TestBareNames();
Report.Section( "rig track names: a second object qualifies its bones" );
TestQualified();
Report.Section( "rig track names: what a person is shown" );
TestDisplay();
}
static void TestBareNames()
{
var (subject, bone) = RigTrackName.Split( "hand_R" );
Report.Check( "a bare name is the main model's", subject == RigTrackName.RootSubject );
Report.Check( "and the bone is left exactly as it was", bone == "hand_R" );
Report.Check( "qualifying against the main model changes nothing",
RigTrackName.Qualify( RigTrackName.RootSubject, "hand_R" ) == "hand_R" );
Report.Check( "and neither does qualifying against null",
RigTrackName.Qualify( null, "hand_R" ) == "hand_R" );
}
static void TestQualified()
{
Report.Check( "an object and a bone join with a slash",
RigTrackName.Qualify( "magazine", "follower" ) == "magazine/follower" );
var (subject, bone) = RigTrackName.Split( "magazine/follower" );
Report.Check( "which splits back to the object", subject == "magazine" );
Report.Check( "and the bone", bone == "follower" );
// The point of the whole scheme: two objects are each allowed a bone called "root", and
// the clip has to be able to tell them apart.
Report.Check( "two objects can both own a bone of the same name",
RigTrackName.Qualify( "door", "root" ) != RigTrackName.Qualify( "frame", "root" ) );
Report.Check( "the object is read back on its own", RigTrackName.SubjectOf( "door/hinge" ) == "door" );
Report.Check( "and so is the bone", RigTrackName.BoneOf( "door/hinge" ) == "hinge" );
// A prop named with a slash would otherwise swallow the bone. First slash wins, so the
// bone survives and only the object name is odd.
Report.Check( "only the first slash separates", RigTrackName.BoneOf( "a/b/c" ) == "b/c" );
Report.Check( "an empty name has no bone and no object",
RigTrackName.Split( "" ).Subject == RigTrackName.RootSubject && RigTrackName.Split( "" ).Bone == "" );
}
static void TestDisplay()
{
Report.Check( "a main-model bone reads exactly as it always did",
RigTrackName.Display( "hand_R" ) == "hand_R" );
Report.Check( "another object's bone says which object it is in",
RigTrackName.Display( "magazine/follower" ) == "follower (magazine)" );
// Three deep - a bone inside a part inside an object. The leaf is the useful half, which is
// why Display splits at the LAST separator where Split uses the first.
Report.Check( "a part's own bone names the part it is in",
RigTrackName.Display( "door/handle/screw" ) == "screw (door/handle)" );
Report.Check( "and Split still answers which object owns it",
RigTrackName.SubjectOf( "door/handle/screw" ) == "door" );
Report.Check( "an empty name displays as nothing rather than throwing",
RigTrackName.Display( "" ) == "" );
}
}