tests/SmartPort/Parser/MeshSkinningTests.cs
using HumanoidRetargeterDmx;
using System.Numerics;
using HumanoidRetargeterVrf;
using HumanoidRetargeterVrf.Blocks;
using HumanoidRetargeterVrf.IO;
using HumanoidRetargeterVrf.ResourceTypes;
using HumanoidRetargeterVrf.Serialization.KeyValues;
using Xunit;
using Dmx = HumanoidRetargeterDmx.HumanoidRetargeterDmx;

namespace SmartPort.Parser.Tests;

public class MeshSkinningTests
{
    sealed class NoMaterials : IFileLoader
    {
        public Resource? LoadFile(string file) => null;
        public Resource? LoadFileCompiled(string file) => null;
    }

    [CompiledFixtureFact]
    public void RecoveredMeshesBindEveryInfluenceToItsOriginalNamedJoint()
    {
        var path = Path.Combine(Environment.GetEnvironmentVariable("HR_SMART_PORT_FIXTURE")!, "models/player/human/frank_mp.vmdl_c");
        using var resource = new Resource();
        resource.Read(path);
        var model = Assert.IsType<Model>(resource.DataBlock);
        var extract = new ModelExtract(resource, new NoMaterials());
        using var content = extract.ToContentFile();
        var checkedInfluences = 0;
        foreach (var mesh in extract.RenderMeshesToExtract)
        {
            using var dmx = Dmx.Load(content.SubFiles.Single(f => f.FileName == Path.GetFileName(mesh.FileName)).Extract());
            var root = Assert.IsType<Element>(dmx.Root!["model"]);
            var joints = Assert.IsAssignableFrom<IList<Element>>(root["jointList"]);
            Assert.Same(root, joints[0]);
            foreach (var bone in model.Skeleton.Bones)
            {
                Assert.Equal(bone.Name, joints[bone.Index + 1].Name);
                var transform = Assert.IsType<Element>(joints[bone.Index + 1]["transform"]);
                Assert.Equal(bone.Position, transform["position"]);
                Assert.Equal(bone.Angle, transform["orientation"]);
            }
            var vertices = dmx.AllElements.Where(e => e.ClassName == "DmeVertexData" && e.ContainsKey("blendindices$0")).ToArray();
            var count = mesh.Mesh.Data.GetSubCollection("m_skeleton").GetInt32Property("m_nBoneWeightCount");
            foreach (var vb in mesh.Mesh.VBIB.VertexBuffers)
            {
                var field = vb.InputLayoutFields.FirstOrDefault(f => f.SemanticName == "BLENDINDICES");
                if (field.SemanticName is null) continue;
                var original = VBIB.GetBlendIndicesArray(vb, field, mesh.BoneRemapTable);
                var stride = original.Length / (int)vb.ElementCount;
                var positions = VBIB.GetVector3AttributeArray(vb, vb.InputLayoutFields.First(f => f.SemanticName == "POSITION"));
                var vertexData = Assert.Single(vertices.Where(e => e["position$0"] is IEnumerable<Vector3> p && p.SequenceEqual(positions)));
                var exported = Assert.IsAssignableFrom<IList<int>>(vertexData["blendindices$0"]);
                Assert.Equal((int)vb.ElementCount * count, exported.Count);
                for (var i = 0; i < vb.ElementCount; i++)
                    for (var j = 0; j < count; j++)
                    {
                        Assert.Equal(model.Skeleton.Bones[original[i * stride + j]].Name, joints[exported[i * count + j]].Name);
                        checkedInfluences++;
                    }
            }
        }
        Assert.True(checkedInfluences > 1000);
    }

    [SkinFixtureFact]
    public void NativeCompiledPortPreservesTargetSkinWeights()
    {
        using var target = new Resource();
        using var port = new Resource();
        target.Read(Environment.GetEnvironmentVariable("HR_SMART_PORT_SKIN_TARGET")!);
        port.Read(Environment.GetEnvironmentVariable("HR_SMART_PORT_SKIN_RESULT")!);
        var original = ReadSkin(Assert.IsType<Model>(target.DataBlock)).ToArray();
        var compiled = ReadSkin(Assert.IsType<Model>(port.DataBlock)).ToArray();
        Assert.NotEmpty(original);
        Assert.NotEmpty(compiled);
        // ModelDoc may reorder/deduplicate vertices. Match by bind position and named weights,
        // never by vertex number or the combined skeleton's new bone indices.
        foreach (var vertex in compiled)
            Assert.True(original.Any(v => Vector3.DistanceSquared(v.Position, vertex.Position) < .000001f
                && v.Weights.Keys.Union(vertex.Weights.Keys).All(n =>
                    MathF.Abs(v.Weights.GetValueOrDefault(n) - vertex.Weights.GetValueOrDefault(n)) < .01f)),
                $"Skin assignment changed at {vertex.Position}: {string.Join(", ", vertex.Weights)}");
    }

    static IEnumerable<(Vector3 Position, Dictionary<string, float> Weights)> ReadSkin(Model model)
    {
        foreach (var mesh in model.GetEmbeddedMeshes())
            foreach (var vb in mesh.Mesh.VBIB.VertexBuffers)
            {
                var field = vb.InputLayoutFields.FirstOrDefault(f => f.SemanticName == "BLENDINDICES");
                if (field.SemanticName is null) continue;
                var indices = VBIB.GetBlendIndicesArray(vb, field, model.GetRemapTable(mesh.MeshIndex));
                var positions = VBIB.GetVector3AttributeArray(vb, vb.InputLayoutFields.First(f => f.SemanticName == "POSITION"));
                var weightField = vb.InputLayoutFields.FirstOrDefault(f => f.SemanticName is "BLENDWEIGHT" or "BLENDWEIGHTS");
                var weights = weightField.SemanticName is null ? null : VBIB.GetBlendWeightsArray(vb, weightField);
                var stride = indices.Length / positions.Length;
                var count = mesh.Mesh.Data.GetSubCollection("m_skeleton").GetInt32Property("m_nBoneWeightCount");
                for (var i = 0; i < positions.Length; i++)
                {
                    var named = new Dictionary<string, float>();
                    for (var j = 0; j < count; j++)
                    {
                        var weight = weights is null ? 1f : weights[i * stride / 4 + j / 4][j % 4];
                        if (weight == 0) continue;
                        var name = model.Skeleton.Bones[indices[i * stride + j]].Name;
                        named[name] = named.GetValueOrDefault(name) + weight;
                    }
                    yield return (positions[i], named);
                }
            }
    }
}

public sealed class SkinFixtureFactAttribute : FactAttribute
{
    public SkinFixtureFactAttribute()
    {
        if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("HR_SMART_PORT_SKIN_TARGET"))
            || string.IsNullOrEmpty(Environment.GetEnvironmentVariable("HR_SMART_PORT_SKIN_RESULT")))
            Skip = "Set HR_SMART_PORT_SKIN_TARGET and HR_SMART_PORT_SKIN_RESULT to the original and natively compiled Smart Port models.";
    }
}