dev/HumanoidRetargeter.Tests/Target/FbxMeshPartsTests.cs
using HumanoidRetargeter.Formats.Fbx;
using HumanoidRetargeter.Target;
using Xunit;
namespace HumanoidRetargeter.Tests.Target;
public class FbxMeshPartsTests
{
[Fact]
public void NamesComeFromConnectedMeshInstancesNotGeometryOrBones()
{
var root = Node("");
var objects = Node("Objects");
objects.Children.AddRange(new[]
{
Node("Geometry", 1L, "Geometry::arbitrary_geometry"),
Node("Model", 2L, "Model::surface.one"),
Node("Model", 3L, "Model::surface.two"),
Node("Model", 4L, "Model::Head"),
Node("Model", 5L, "Model::unused"),
});
var connections = Node("Connections");
connections.Children.AddRange(new[]
{
Node("C", "OO", 1L, 2L),
Node("C", "OO", 1L, 3L), // instanced geometry must keep both instances
Node("C", "OO", 2L, 4L),
Node("C", "OO", 1L, 2L), // repeated connection must not duplicate a mesh
Node("C", "OP", 1L, 5L),
Node("C", "OO"),
});
root.Children.AddRange(new[] { objects, connections });
Assert.Equal(new[] { "surface.one", "surface.two" },
FbxMeshParts.ReadNames(FbxBinaryWriter.Write(root)));
}
[Fact]
public void MissingConnectionsDoNotFilterOutTheWholeModel()
{
var root = Node("");
root.Children.Add(Node("Objects"));
Assert.Empty(FbxMeshParts.ReadNames(FbxBinaryWriter.Write(root)));
}
[Fact]
public void SeparateImportsKeepNamesScaleAndMaterialRemaps()
{
var remaps = new Dictionary<string, string> { ["eyes.vmat"] = "models/eyes.vmat" };
var text = VmdlWriter.GenerateStandalone("", Array.Empty<AnimEntry>(), 1, "Hips",
meshFilePath: "models/person.fbx", meshImportScale: 100, materialRemaps: remaps,
meshImportNames: new[] { "surface.one", "surface.two" });
var meshes = Meshes(text);
Assert.Equal(2, meshes.Count);
for (var i = 0; i < meshes.Count; i++)
{
Assert.Equal("models/person.fbx", meshes[i].GetString("filename"));
Assert.Equal(100, Assert.IsType<KvDouble>(meshes[i]["import_scale"]).Value);
Assert.Equal($"person_part_{i}", meshes[i].GetString("name"));
var filter = Assert.IsType<KvObject>(meshes[i]["import_filter"]);
Assert.True(Assert.IsType<KvBool>(filter["exclude_by_default"]).Value);
var names = Assert.IsType<KvArray>(filter["exception_list"]);
Assert.Equal(i == 0 ? "surface.one" : "surface.two",
Assert.IsType<KvString>(Assert.Single(names.Items)).Value);
}
Assert.Contains("models/eyes.vmat", text);
}
[Fact]
public void LegacyOutputIsUpgradedWithoutDroppingAnimationsAndIsIdempotent()
{
var text = VmdlWriter.GenerateStandalone("", new[]
{
new AnimEntry { Name = "walk", SourceFilename = "models/walk.dmx" },
}, 1, "Hips", meshFilePath: "models/person.fbx");
var names = new[] { "body", "eyes" };
var updated = VmdlAugmenter.EnsureMeshFile(text, "models/person.fbx", 1,
meshImportNames: names);
Assert.Equal(2, Meshes(updated).Count);
Assert.Contains("models/walk.dmx", updated);
Assert.Equal(updated, VmdlAugmenter.EnsureMeshFile(updated, "models/person.fbx", 1,
meshImportNames: names));
}
static List<KvObject> Meshes(string text)
{
var root = (KvObject)((KvObject)Kv3.Parse(text).Root)["rootNode"];
var list = ((KvArray)root["children"]).Items.OfType<KvObject>()
.Single(n => n.GetString("_class") == "RenderMeshList");
return ((KvArray)list["children"]).Items.Cast<KvObject>().ToList();
}
static FbxNode Node(string name, params object[] properties)
{
var node = new FbxNode(name);
node.Properties.AddRange(properties);
return node;
}
}