Code/Formats/Fbx/FbxSkinnedMeshes.cs
#nullable enable
namespace HumanoidHandRetargeter.Formats.Fbx;
/// <summary>Selects skinned target meshes without importing loose scene props as hands.</summary>
public static class FbxSkinnedMeshes
{
public static IReadOnlyList<string> ReadNames(byte[] bytes)
{
var root=FbxTokenizer.Parse(bytes);
var objects=root.Child("Objects")?.Children??new List<FbxNode>();
var links=root.Child("Connections")?.ChildrenNamed("C")
.Where(n=>n.Properties.Count>=3&&n.Prop<string>(0)=="OO").ToArray()??Array.Empty<FbxNode>();
var skins=objects.Where(n=>n.Name=="Deformer"&&n.Properties.Count>=3&&n.Prop<string>(2)=="Skin").Select(n=>n.Prop<long>(0)).ToHashSet();
var geometry=links.Where(n=>skins.Contains(n.Prop<long>(1))).Select(n=>n.Prop<long>(2)).ToHashSet();
var models=links.Where(n=>geometry.Contains(n.Prop<long>(1))).Select(n=>n.Prop<long>(2)).ToHashSet();
// Avoid introducing an unnecessary name filter when every mesh is skinned.
if(objects.Where(n=>n.Name=="Model"&&n.Properties.Count>=3&&n.Prop<string>(2)=="Mesh")
.All(n=>models.Contains(n.Prop<long>(0))))return Array.Empty<string>();
return objects.Where(n=>n.Name=="Model"&&models.Contains(n.Prop<long>(0)))
.Select(n=>FbxNode.SplitName(n.Prop<string>(1)).Name)
.SelectMany(name=>new[]{name,name[(name.LastIndexOf(':')+1)..]})
.Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
}
}