using System.Numerics; using Elements.Assets; using SharpGLTF.Geometry; using SharpGLTF.Geometry.VertexTypes; using SharpGLTF.Materials; using SharpGLTF.Schema2; namespace Res2tf; public class MeshAsset(MeshX meshX, ModelRoot model) { public MeshX MeshX { get; } = meshX; public Mesh Mesh { get; } = model.CreateMesh(Build(meshX)); static IMeshBuilder Build(MeshX meshX) => (meshX.HasNormals, meshX.HasTangents) switch { (false, false) => Build(meshX), ( true, false) => Build(meshX), ( true, true) => Build(meshX), _ => throw new InvalidOperationException(), }; static IMeshBuilder Build(MeshX meshX) where G : struct, IVertexGeometry => (meshX.HasColors, meshX.UV_ChannelCount) switch { (false, 0) => Build(meshX), (false, 1) => Build(meshX), (false, 2) => Build(meshX), (false, 3) => Build(meshX), (false, 4) => Build(meshX), ( true, 0) => Build(meshX), ( true, 1) => Build(meshX), ( true, 2) => Build(meshX), ( true, 3) => Build(meshX), ( true, 4) => Build(meshX), _ => throw new InvalidOperationException(), }; static IMeshBuilder Build(MeshX meshX) where G : struct, IVertexGeometry where M : struct, IVertexMaterial => meshX.HasBoneBindings ? Build(meshX) : Build(meshX); static MeshBuilder Build(MeshX meshX) where G : struct, IVertexGeometry where M : struct, IVertexMaterial where S : struct, IVertexSkinning { var mesh = new MeshBuilder(); foreach (var subMeshX in meshX.Submeshes) { var prim = mesh.UsePrimitive(new()); // placeholder materials VertexBuilder BuildVertex(Vertex v) { var vertex = new VertexBuilder { Position = v.Position }; if (meshX.HasNormals ) vertex.Geometry.SetNormal(v.Normal); if (meshX.HasTangents) vertex.Geometry.SetTangent(v.Tangent4); if (meshX.HasColors ) vertex.Material.SetColor(0, new(v.Color.r, v.Color.g, v.Color.b, v.Color.a)); for (var i = 0; i < meshX.UV_ChannelCount; i++) vertex.Material.SetTexCoord(i, v.GetUV(i)); if (meshX.HasBoneBindings) { var b = v.BoneBinding; vertex.Skinning.SetBindings( (b.boneIndex0, b.weight0), (b.boneIndex1, b.weight1), (b.boneIndex2, b.weight2), (b.boneIndex3, b.weight3)); } return vertex; } foreach (var t in subMeshX.RawIndicies.Chunk(3)) prim.AddTriangle(BuildVertex(meshX.GetVertex(t[0])), BuildVertex(meshX.GetVertex(t[1])), BuildVertex(meshX.GetVertex(t[2]))); } return mesh; } public IEnumerable GetInverseBindMatrices() => MeshX.Bones .Select(b => b.BindPose) .Select(ResoniteToNumerics) .Select(NormalizeIdentityColumn); static Matrix4x4 ResoniteToNumerics(Elements.Core.float4x4 m) => new(m.m00, m.m10, m.m20, m.m30, m.m01, m.m11, m.m21, m.m31, m.m02, m.m12, m.m22, m.m32, m.m03, m.m13, m.m23, m.m33); // glTF is very sensitive about the identity column being exactly (0, 0, 0, 1). static Matrix4x4 NormalizeIdentityColumn(Matrix4x4 m) => (IsApproxEqual(m.M14, 0) && IsApproxEqual(m.M24, 0) && IsApproxEqual(m.M34, 0) && IsApproxEqual(m.M44, 1)) ? m with { M14 = 0, M24 = 0, M34 = 0, M44 = 1 } : m; static bool IsApproxEqual(float a, float b, float epsilon = 0.00001f) => MathF.Abs(a - b) < epsilon; }