diff --git a/Penumbra/Import/Models/Export/MeshExporter.cs b/Penumbra/Import/Models/Export/MeshExporter.cs index c6edd1c0..2cffd3c3 100644 --- a/Penumbra/Import/Models/Export/MeshExporter.cs +++ b/Penumbra/Import/Models/Export/MeshExporter.cs @@ -315,25 +315,10 @@ public class MeshExporter MdlFile.VertexType.NByte4 => new Vector4(reader.ReadByte() / 255f, reader.ReadByte() / 255f, reader.ReadByte() / 255f, reader.ReadByte() / 255f), MdlFile.VertexType.Half2 => new Vector2((float)reader.ReadHalf(), (float)reader.ReadHalf()), MdlFile.VertexType.Half4 => new Vector4((float)reader.ReadHalf(), (float)reader.ReadHalf(), (float)reader.ReadHalf(), (float)reader.ReadHalf()), - MdlFile.VertexType.UShort4 => ReadUShort4(reader), + MdlFile.VertexType.UShort4 => reader.ReadBytes(8), var other => throw _notifier.Exception($"Unhandled vertex type {other}"), }; } - - private byte[] ReadUShort4(BinaryReader reader) - { - var buffer = reader.ReadBytes(8); - var byteValues = new byte[8]; - byteValues[0] = buffer[0]; - byteValues[4] = buffer[1]; - byteValues[1] = buffer[2]; - byteValues[5] = buffer[3]; - byteValues[2] = buffer[4]; - byteValues[6] = buffer[5]; - byteValues[3] = buffer[6]; - byteValues[7] = buffer[7]; - return byteValues; - } /// Get the vertex geometry type for this mesh's vertex usages. private Type GetGeometryType(IReadOnlyDictionary usages) diff --git a/Penumbra/Import/Models/Import/MeshImporter.cs b/Penumbra/Import/Models/Import/MeshImporter.cs index e3567780..813ef422 100644 --- a/Penumbra/Import/Models/Import/MeshImporter.cs +++ b/Penumbra/Import/Models/Import/MeshImporter.cs @@ -196,7 +196,9 @@ public class MeshImporter(IEnumerable nodes, IoNotifier notifier) // Per glTF specification, an asset with a skin MUST contain skinning attributes on its meshes. var joints0Accessor = primitive.GetVertexAccessor("JOINTS_0")?.AsVector4Array(); var weights0Accessor = primitive.GetVertexAccessor("WEIGHTS_0")?.AsVector4Array(); - + var joints1Accessor = primitive.GetVertexAccessor("JOINTS_1")?.AsVector4Array(); + var weights1Accessor = primitive.GetVertexAccessor("WEIGHTS_1")?.AsVector4Array(); + if (joints0Accessor == null || weights0Accessor == null) throw notifier.Exception($"Primitive {primitiveIndex} is skinned but does not contain skinning vertex attributes."); @@ -205,6 +207,8 @@ public class MeshImporter(IEnumerable nodes, IoNotifier notifier) { var joints = joints0Accessor[i]; var weights = weights0Accessor[i]; + var joints1 = joints1Accessor?[i]; + var weights1 = weights1Accessor?[i]; for (var index = 0; index < 4; index++) { // If a joint has absolutely no weight, we omit the bone entirely. @@ -212,26 +216,11 @@ public class MeshImporter(IEnumerable nodes, IoNotifier notifier) continue; usedJoints.Add((ushort)joints[index]); - } - } - - var joints1Accessor = primitive.GetVertexAccessor("JOINTS_1")?.AsVector4Array(); - var weights1Accessor = primitive.GetVertexAccessor("WEIGHTS_1")?.AsVector4Array(); - - if (joints1Accessor == null || weights1Accessor == null) - continue; - - for (var i = 0; i < joints1Accessor.Count; i++) - { - var joints = joints1Accessor[i]; - var weights = weights1Accessor[i]; - for (var index = 0; index < 4; index++) - { - // If a joint has absolutely no weight, we omit the bone entirely. - if (weights[index] == 0) - continue; - - usedJoints.Add((ushort)joints[index]); + + if (joints1 != null && weights1 != null && weights1.Value[index] != 0) + { + usedJoints.Add((ushort)joints1.Value[index]); + } } } } diff --git a/Penumbra/Import/Models/Import/VertexAttribute.cs b/Penumbra/Import/Models/Import/VertexAttribute.cs index b71ad429..12ceba23 100644 --- a/Penumbra/Import/Models/Import/VertexAttribute.cs +++ b/Penumbra/Import/Models/Import/VertexAttribute.cs @@ -132,72 +132,50 @@ public class VertexAttribute { if (!accessors.ContainsKey("JOINTS_1")) throw notifier.Exception("Mesh contained WEIGHTS_1 attribute but no corresponding JOINTS_1 attribute."); - - var element = new MdlStructs.VertexElement() - { - Stream = 0, - Type = (byte)MdlFile.VertexType.UShort4, - Usage = (byte)MdlFile.VertexUsage.BlendWeights, - }; - - var weights0 = weights0Accessor.AsVector4Array(); - var weights1 = weights1Accessor.AsVector4Array(); - - return new VertexAttribute( - element, - index => { - var weight0 = weights0[index]; - var weight1 = weights1[index]; - var originalData = BuildUshort4(weight0, weight1); - var byteValues = originalData.Select(x => (byte)Math.Round(x * 255f)).ToArray(); - return AdjustByteArray(byteValues, originalData); - } - ); } - else + + var element = new MdlStructs.VertexElement() { - var element = new MdlStructs.VertexElement() + Stream = 0, + Type = (byte)MdlFile.VertexType.UShort4, + Usage = (byte)MdlFile.VertexUsage.BlendWeights, + }; + + var weights0 = weights0Accessor.AsVector4Array(); + var weights1 = weights1Accessor?.AsVector4Array(); + + return new VertexAttribute( + element, + index => { + var weight0 = weights0[index]; + var weight1 = weights1?[index]; + var originalData = BuildUshort4(weight0, weight1 ?? Vector4.Zero); + var byteValues = originalData.Select(x => (byte)Math.Round(x * 255f)).ToArray(); + return AdjustByteArray(byteValues, originalData); + } + ); + + /*var element = new MdlStructs.VertexElement() + { + Stream = 0, + Type = (byte)MdlFile.VertexType.NByte4, + Usage = (byte)MdlFile.VertexUsage.BlendWeights, + }; + + var weights0 = weights0Accessor.AsVector4Array(); + + return new VertexAttribute( + element, + index => { - Stream = 0, - Type = (byte)MdlFile.VertexType.UShort4, - Usage = (byte)MdlFile.VertexUsage.BlendWeights, - }; - - var weights0 = weights0Accessor.AsVector4Array(); - - return new VertexAttribute( - element, - index => { - var weight0 = weights0[index]; - var weight1 = Vector4.Zero; - var originalData = BuildUshort4(weight0, weight1); - var byteValues = originalData.Select(x => (byte)Math.Round(x * 255f)).ToArray(); - return AdjustByteArray(byteValues, originalData); - } - ); - - /*var element = new MdlStructs.VertexElement() - { - Stream = 0, - Type = (byte)MdlFile.VertexType.NByte4, - Usage = (byte)MdlFile.VertexUsage.BlendWeights, - }; - - var weights0 = weights0Accessor.AsVector4Array(); - - return new VertexAttribute( - element, - index => - { - var weight0 = weights0[index]; - var originalData = new[] { weight0.X, weight0.Y, weight0.Z, weight0.W }; - var byteValues = originalData.Select(x => (byte)Math.Round(x * 255f)).ToArray(); - var newByteValues = AdjustByteArray(byteValues, originalData); - if (!newByteValues.SequenceEqual(byteValues)) - notifier.Warning("Adjusted blend weights to maintain precision."); - return newByteValues; - });*/ - } + var weight0 = weights0[index]; + var originalData = new[] { weight0.X, weight0.Y, weight0.Z, weight0.W }; + var byteValues = originalData.Select(x => (byte)Math.Round(x * 255f)).ToArray(); + var newByteValues = AdjustByteArray(byteValues, originalData); + if (!newByteValues.SequenceEqual(byteValues)) + notifier.Warning("Adjusted blend weights to maintain precision."); + return newByteValues; + });*/ } private static byte[] AdjustByteArray(byte[] byteValues, float[] originalValues) @@ -241,100 +219,77 @@ public class VertexAttribute if (boneMap == null) throw notifier.Exception("Mesh contained JOINTS_0 attribute but no bone mapping was created."); - var joints0 = joints0Accessor.AsVector4Array(); - var weights0 = weights0Accessor.AsVector4Array(); - - if (accessors.TryGetValue("JOINTS_1", out var joints1Accessor)) + var joints0 = joints0Accessor.AsVector4Array(); + var weights0 = weights0Accessor.AsVector4Array(); + accessors.TryGetValue("JOINTS_1", out var joints1Accessor); + accessors.TryGetValue("WEIGHTS_1", out var weights1Accessor); + var element = new MdlStructs.VertexElement { - if (!accessors.TryGetValue("WEIGHTS_1", out var weights1Accessor)) - throw notifier.Exception("Mesh contained JOINTS_1 attribute but no corresponding WEIGHTS_1 attribute."); + Stream = 0, + Type = (byte)MdlFile.VertexType.UShort4, + Usage = (byte)MdlFile.VertexUsage.BlendIndices, + }; - var element = new MdlStructs.VertexElement + var joints1 = joints1Accessor?.AsVector4Array(); + var weights1 = weights1Accessor?.AsVector4Array(); + + return new VertexAttribute( + element, + index => { - Stream = 0, - Type = (byte)MdlFile.VertexType.UShort4, - Usage = (byte)MdlFile.VertexUsage.BlendIndices, - }; - - var joints1 = joints1Accessor.AsVector4Array(); - var weights1 = weights1Accessor.AsVector4Array(); - - return new VertexAttribute( - element, - index => + var gltfIndices0 = joints0[index]; + var gltfWeights0 = weights0[index]; + var gltfIndices1 = joints1?[index]; + var gltfWeights1 = weights1?[index]; + var v0 = new Vector4( + gltfWeights0.X == 0 ? 0 : boneMap[(ushort)gltfIndices0.X], + gltfWeights0.Y == 0 ? 0 : boneMap[(ushort)gltfIndices0.Y], + gltfWeights0.Z == 0 ? 0 : boneMap[(ushort)gltfIndices0.Z], + gltfWeights0.W == 0 ? 0 : boneMap[(ushort)gltfIndices0.W] + ); + + Vector4 v1; + if (gltfIndices1 != null && gltfWeights1 != null) { - var gltfIndices0 = joints0[index]; - var gltfWeights0 = weights0[index]; - var gltfIndices1 = joints1[index]; - var gltfWeights1 = weights1[index]; - var v0 = new Vector4( - gltfWeights0.X == 0 ? 0 : boneMap[(ushort)gltfIndices0.X], - gltfWeights0.Y == 0 ? 0 : boneMap[(ushort)gltfIndices0.Y], - gltfWeights0.Z == 0 ? 0 : boneMap[(ushort)gltfIndices0.Z], - gltfWeights0.W == 0 ? 0 : boneMap[(ushort)gltfIndices0.W] + v1 = new Vector4( + gltfWeights1.Value.X == 0 ? 0 : boneMap[(ushort)gltfIndices1.Value.X], + gltfWeights1.Value.Y == 0 ? 0 : boneMap[(ushort)gltfIndices1.Value.Y], + gltfWeights1.Value.Z == 0 ? 0 : boneMap[(ushort)gltfIndices1.Value.Z], + gltfWeights1.Value.W == 0 ? 0 : boneMap[(ushort)gltfIndices1.Value.W] ); - var v1 = new Vector4( - gltfWeights1.X == 0 ? 0 : boneMap[(ushort)gltfIndices1.X], - gltfWeights1.Y == 0 ? 0 : boneMap[(ushort)gltfIndices1.Y], - gltfWeights1.Z == 0 ? 0 : boneMap[(ushort)gltfIndices1.Z], - gltfWeights1.W == 0 ? 0 : boneMap[(ushort)gltfIndices1.W] - ); - - var byteValues = BuildUshort4(v0, v1); - - return byteValues.Select(x => (byte)x).ToArray(); } - ); - } - else + else + { + v1 = Vector4.Zero; + } + + var byteValues = BuildUshort4(v0, v1); + + return byteValues.Select(x => (byte)x).ToArray(); + } + ); + + /*var element = new MdlStructs.VertexElement() { - var element = new MdlStructs.VertexElement - { - Stream = 0, - Type = (byte)MdlFile.VertexType.UShort4, - Usage = (byte)MdlFile.VertexUsage.BlendIndices, - }; - - return new VertexAttribute( - element, - index => - { - var gltfIndices0 = joints0[index]; - var gltfWeights0 = weights0[index]; - var v0 = new Vector4( - gltfWeights0.X == 0 ? 0 : boneMap[(ushort)gltfIndices0.X], - gltfWeights0.Y == 0 ? 0 : boneMap[(ushort)gltfIndices0.Y], - gltfWeights0.Z == 0 ? 0 : boneMap[(ushort)gltfIndices0.Z], - gltfWeights0.W == 0 ? 0 : boneMap[(ushort)gltfIndices0.W] - ); - var v1 = Vector4.Zero; - var byteValues = BuildUshort4(v0, v1); + Stream = 0, + Type = (byte)MdlFile.VertexType.UByte4, + Usage = (byte)MdlFile.VertexUsage.BlendIndices, + }; - return byteValues.Select(x => (byte)x).ToArray(); - } - ); - /*var element = new MdlStructs.VertexElement() + return new VertexAttribute( + element, + index => { - Stream = 0, - Type = (byte)MdlFile.VertexType.UByte4, - Usage = (byte)MdlFile.VertexUsage.BlendIndices, - }; - - return new VertexAttribute( - element, - index => - { - var gltfIndices = joints0[index]; - var gltfWeights = weights0[index]; - return BuildUByte4(new Vector4( - gltfWeights.X == 0 ? 0 : boneMap[(ushort)gltfIndices.X], - gltfWeights.Y == 0 ? 0 : boneMap[(ushort)gltfIndices.Y], - gltfWeights.Z == 0 ? 0 : boneMap[(ushort)gltfIndices.Z], - gltfWeights.W == 0 ? 0 : boneMap[(ushort)gltfIndices.W] - )); - } - );*/ - } + var gltfIndices = joints0[index]; + var gltfWeights = weights0[index]; + return BuildUByte4(new Vector4( + gltfWeights.X == 0 ? 0 : boneMap[(ushort)gltfIndices.X], + gltfWeights.Y == 0 ? 0 : boneMap[(ushort)gltfIndices.Y], + gltfWeights.Z == 0 ? 0 : boneMap[(ushort)gltfIndices.Z], + gltfWeights.W == 0 ? 0 : boneMap[(ushort)gltfIndices.W] + )); + } + );*/ } public static VertexAttribute? Normal(Accessors accessors, IEnumerable morphAccessors) @@ -620,18 +575,11 @@ public class VertexAttribute (byte)Math.Round(input.Z * 255f), (byte)Math.Round(input.W * 255f), ]; - - private static float[] BuildUshort4(Vector4 v0, Vector4 v1) - { - var buf = new float[8]; - buf[0] = v0.X; - buf[4] = v1.X; - buf[1] = v0.Y; - buf[5] = v1.Y; - buf[2] = v0.Z; - buf[6] = v1.Z; - buf[3] = v0.W; - buf[7] = v1.W; - return buf; - } + + private static float[] BuildUshort4(Vector4 v0, Vector4 v1) => + new[] + { + v0.X, v0.Y, v0.Z, v0.W, + v1.X, v1.Y, v1.Z, v1.W, + }; }