tests/CitizenAnimationSetup/SmartPortTests.cs
using System.Numerics;
using HumanoidRetargeter.Maths;
using HumanoidRetargeter.Skeleton;
using HumanoidRetargeter.Target;
using Xunit;
using SkeletonModel = HumanoidRetargeter.Skeleton.Skeleton;
namespace HumanoidRetargeter.Tests.Target;
public class SmartPortTests
{
static string Model(string nodes, string fields = "") => VmdlWriter.Kv3Header
+ "{ rootNode = { _class = \"RootNode\" children = [" + nodes + "] " + fields + " } }";
const string Mesh = "{ _class = \"RenderMeshList\" children = [{ filename = \"target.dmx\" }] }";
[Fact]
public void TransfersWholeSourceSetupButKeepsTargetGeometry()
{
var source = Model("""
{ _class = "RenderMeshList" children = [{ filename = "source.dmx" }] },
{ _class = "MaterialGroupList" children = [{ material = "source.vmat" }] },
{ _class = "AnimationList" children = [{ _class = "Folder" children = [
{ _class = "AnimFile" name = "walk" source_filename = "walk.dmx" children = [
{ _class = "AnimSubtract" anim_name = "idle" },
{ _class = "AnimEvent" event_class = "AE_FOOTSTEP" event_frame = 7 event_keys = { Foot = "1" } }
] }
] }] },
{ _class = "AnimConstraintList" children = [{ name = "CopyPinky" }] },
{ _class = "FutureRigExtension" value = 42 }
""", "anim_graph_name = \"source.vanmgrph\" model_archetype = \"character\"");
var target = Model(Mesh + """
, { _class = "MaterialGroupList" children = [{ material = "target.vmat" }] },
{ _class = "PhysicsShapeList" children = [{ radius = 12 }] },
{ _class = "AnimationList" children = [{ name = "old" }] }
""");
var result = SmartPortSetup.Apply(target, source, "graphs/ported.vanmgrph");
Assert.Contains("target.dmx", result);
Assert.Contains("target.vmat", result);
Assert.DoesNotContain("source.dmx", result);
Assert.DoesNotContain("source.vmat", result);
Assert.DoesNotContain("\"old\"", result);
Assert.Equal("graphs/ported.vanmgrph", StockAnimationGraph.GraphName(result));
foreach (var category in new[] { "AnimationList", "AnimConstraintList", "FutureRigExtension" })
Assert.True(KvValue.DeepEquals(Node(source, category), Node(result, category)));
Assert.True(KvValue.DeepEquals(Node(target, "PhysicsShapeList"), Node(result, "PhysicsShapeList")));
}
[Fact]
public void DifferentSourceUnitsAreRejectedEvenForMatchingCompiledBones()
{
var scaled = Model(Mesh + ", { _class = \"ModelModifierList\" children = [{ scale = 0.3937 }] }");
Assert.Throws<InvalidOperationException>(() => SmartPortSetup.Apply(scaled, Model(Mesh), "x.vanmgrph"));
}
[Fact]
public void BaseModelsAndMissingMeshOrGraphAreRejected()
{
Assert.Throws<InvalidOperationException>(() => SmartPortSetup.Apply(Model(Mesh, "base_model_name = \"base.vmdl\""), Model(Mesh), "x.vanmgrph"));
Assert.Throws<InvalidOperationException>(() => SmartPortSetup.Apply(Model(""), Model(Mesh), "x.vanmgrph"));
Assert.Throws<InvalidOperationException>(() => SmartPortSetup.Apply(Model(Mesh), Model(Mesh), ""));
}
[Fact]
public void RebaseChangesOnlyRecoveredPathsIncludingArrayReferences()
{
var input = Model(Mesh + ", { _class = \"Other\" refs = [\"target.dmx\", \"installed.vmat\"] }");
var output = SmartPortSetup.Rebase(input, new Dictionary<string, string> { ["target.dmx"] = "port/target.dmx" });
Assert.Equal(2, output.Split("port/target.dmx").Length - 1);
Assert.Contains("installed.vmat", output);
}
static SkeletonModel Rig(float length = 1, string? parent = "root", bool extra = false)
{
var bones = new List<BoneDefinition> { new("root", null, XForm.Identity), new("arm", parent, new XForm(new Vector3(length, 0, 0), Quaternion.Identity)) };
if (extra) bones.Add(new("eye", "root", XForm.Identity));
return SkeletonModel.Create(bones);
}
[Fact]
public void CompatibilityChecksWholeHierarchyAndBindNotJustNames()
{
Assert.Null(SmartPortSetup.CompatibilityError(Rig(extra: true), Rig()));
Assert.Contains("missing", SmartPortSetup.CompatibilityError(Rig(), Rig(extra: true)));
Assert.Contains("parent", SmartPortSetup.CompatibilityError(Rig(parent: null), Rig()));
Assert.Contains("bind pose", SmartPortSetup.CompatibilityError(Rig(2), Rig()));
Assert.Null(SmartPortSetup.CompatibilityError(Rig(1.001f), Rig()));
}
static KvObject Node(string model, string category)
=> ((KvArray)((KvObject)((KvObject)Kv3.Parse(model).Root)["rootNode"])["children"]).Items.OfType<KvObject>().Single(n => n.GetString("_class") == category);
}