From 64fd54635e113ab4c71684d2f60a4b35df807c5f Mon Sep 17 00:00:00 2001 From: bird_egop Date: Sat, 6 Jun 2026 23:25:14 +0300 Subject: [PATCH] fix textures UV. drop leftovers. properly map material_lo. --- MaterialLib/MaterialParser.cs | 26 +---------------- MshLib/Msh0x01.cs | 25 ---------------- MshLib/Msh0x0D.cs | 29 +++++++++++-------- MshLib/MshConverter.cs | 2 +- .../Viewport/Msh/MshRestPoseBuilder.cs | 2 +- .../Viewport/Msh/MshViewportLoader.cs | 6 ++-- .../Viewport/Msh/WeaMaterialLibrary.cs | 8 +++-- NResUI/Rendering/Viewport/ViewportMaterial.cs | 7 ++++- TexmLib/TexmFile.cs | 4 +-- 9 files changed, 36 insertions(+), 73 deletions(-) diff --git a/MaterialLib/MaterialParser.cs b/MaterialLib/MaterialParser.cs index 449fb9e..cf337dd 100644 --- a/MaterialLib/MaterialParser.cs +++ b/MaterialLib/MaterialParser.cs @@ -1,5 +1,6 @@ using System.Buffers.Binary; using System.Text; +using Common; namespace MaterialLib; @@ -168,28 +169,3 @@ public static class MaterialParser return $"Интерполируется: {string.Join(", ", parts)} | Остальные компоненты копируются (Flags: 0x{(int)target:X})"; } } - -// Helper extensions -internal static class StreamExtensions -{ - public static float ReadFloatLittleEndian(this Stream stream) - { - Span buffer = stackalloc byte[4]; - stream.ReadExactly(buffer); - return BinaryPrimitives.ReadSingleLittleEndian(buffer); - } - - public static ushort ReadUInt16LittleEndian(this Stream stream) - { - Span buffer = stackalloc byte[2]; - stream.ReadExactly(buffer); - return BinaryPrimitives.ReadUInt16LittleEndian(buffer); - } - - public static uint ReadUInt32LittleEndian(this Stream stream) - { - Span buffer = stackalloc byte[4]; - stream.ReadExactly(buffer); - return BinaryPrimitives.ReadUInt32LittleEndian(buffer); - } -} diff --git a/MshLib/Msh0x01.cs b/MshLib/Msh0x01.cs index 4e440ba..85183fc 100644 --- a/MshLib/Msh0x01.cs +++ b/MshLib/Msh0x01.cs @@ -104,11 +104,6 @@ public static class Msh0x01 ushort FallbackKey0x08, ushort[] Msh02SlotIndicesByStateAndLOD) { - public bool IsRoot => ParentIndexOrLink == ushort.MaxValue; - - public int ParentIndexOrMinusOne => - ParentIndexOrLink == ushort.MaxValue ? -1 : ParentIndexOrLink; - public ushort ResolveSlotIndex(int state, int lod = 0) { @@ -129,26 +124,6 @@ public static class Msh0x01 ? Msh02SlotIndicesByStateAndLOD[index] : ushort.MaxValue; } - - public bool HasGeometrySlot(int lod, int group = 0) => - ResolveSlotIndex(lod, group) != ushort.MaxValue; - - public int CountLodsForGroup(int group = 0) - { - var count = 0; - - for (var lod = 0; lod < StateCount; lod++) - { - if (!HasGeometrySlot(lod, group)) - { - break; - } - - count++; - } - - return count; - } } } diff --git a/MshLib/Msh0x0D.cs b/MshLib/Msh0x0D.cs index 80e9433..5f9eba5 100644 --- a/MshLib/Msh0x0D.cs +++ b/MshLib/Msh0x0D.cs @@ -38,21 +38,25 @@ public static class Msh0x0D return data .Chunk(ElementSize) .Select(x => new Batch( - (BatchFlags)BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0x00)), - BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0x02)), - BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0x04)), - BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0x06)), - BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0x08)), - BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(0x0A)), - BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0x0E)), - BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(0x10)))) + Flags: (BatchFlags)BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0x00)), + MaterialIndexHi: BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0x02)), + MaterialIndexLo: x.AsSpan(0x04)[0], + LightmapIndex: x.AsSpan(0x05)[0], + LocalBatchIndex: BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0x06)), + IndexCount0x06: BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0x08)), + IndexStart0x06: BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(0x0A)), + VertexCount0x03: BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0x0E)), + BaseVertex0x03: BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0x10)) + ) + ) .ToList(); } /// MSH batch 0x0D, sizeof 0x14. /// [0x00..0x02] Флаги batch. - /// [0x02..0x04] Индекс material slot. Может быть overridden CAniMesh::forced_material_index. - /// [0x04..0x06] Opaque. В GetBatchRenderData попадает в packed field вместе с material/lightmap данными. + /// [0x02..0x04] Индекс material slot. Может быть overridden CAniMesh::forced_material_index. + /// [0x04..0x05] Индекс материала + /// [0x05..0x06] Индекс карты освещения /// [0x06..0x08] Локальный batch index. В GetBatchRenderData складывается с MSH_piece.local_index_base / batch base. /// [0x08..0x0A] Количество индексов из component 0x06. /// [0x0A..0x0E] Первый индекс в component 0x06. @@ -60,8 +64,9 @@ public static class Msh0x0D /// [0x10..0x14] Base vertex в vertex streams, включая position stream 0x03. public readonly record struct Batch( BatchFlags Flags, - ushort MaterialIndex, - ushort Opaque04, + ushort MaterialIndexHi, + byte MaterialIndexLo, + byte LightmapIndex, ushort LocalBatchIndex, ushort IndexCount0x06, uint IndexStart0x06, diff --git a/MshLib/MshConverter.cs b/MshLib/MshConverter.cs index 67e122a..cbe3d0b 100644 --- a/MshLib/MshConverter.cs +++ b/MshLib/MshConverter.cs @@ -185,7 +185,7 @@ public sealed class MshConverter continue; } - writer.WriteLine($"# batch {batchIndex}, material {batch.MaterialIndex}, flags {batch.Flags}"); + writer.WriteLine($"# batch {batchIndex}, material {batch.MaterialIndexLo}, flags {batch.Flags}"); for (var i = 0; i + 2 < batch.IndexCount0x06; i += 3) { diff --git a/NResUI/Rendering/Viewport/Msh/MshRestPoseBuilder.cs b/NResUI/Rendering/Viewport/Msh/MshRestPoseBuilder.cs index 8b97065..24a1619 100644 --- a/NResUI/Rendering/Viewport/Msh/MshRestPoseBuilder.cs +++ b/NResUI/Rendering/Viewport/Msh/MshRestPoseBuilder.cs @@ -88,7 +88,7 @@ public static class MshRestPoseBuilder { try { - return Convert.ToInt32(node.ParentIndexOrMinusOne); + return Convert.ToInt32(node.ParentIndexOrLink); } catch { diff --git a/NResUI/Rendering/Viewport/Msh/MshViewportLoader.cs b/NResUI/Rendering/Viewport/Msh/MshViewportLoader.cs index 701bdc3..68ad944 100644 --- a/NResUI/Rendering/Viewport/Msh/MshViewportLoader.cs +++ b/NResUI/Rendering/Viewport/Msh/MshViewportLoader.cs @@ -134,7 +134,7 @@ public static class MshViewportLoader var outIndices = new List(); var batchTriangleCount = 0; - var material = materialLibrary.FindMaterial(batch.MaterialIndex) ?? ViewportMaterial.Untextured; + var material = materialLibrary.FindMaterial(batch.MaterialIndexLo) ?? ViewportMaterial.Untextured; var vertexColor = material.HasTexture ? Vector3.One : debugColor; for (var i = 0; i + 2 < batch.IndexCount0x06; i += 3) @@ -156,7 +156,7 @@ public static class MshViewportLoader normal = Vector3.UnitY; else normal = Vector3.Normalize(normal); - + AddTriangleVertex(p0, vertexColor, normal, GetUv(uvs, vertexIndex0), vertices, outIndices, ref boundsMin, ref boundsMax); AddTriangleVertex(p1, vertexColor, normal, GetUv(uvs, vertexIndex1), vertices, outIndices, ref boundsMin, ref boundsMax); AddTriangleVertex(p2, vertexColor, normal, GetUv(uvs, vertexIndex2), vertices, outIndices, ref boundsMin, ref boundsMax); @@ -183,7 +183,7 @@ public static class MshViewportLoader return Vector2.Zero; var uv = uvs[vertexIndex]; - return new Vector2(uv.U / 1024.0f, 1.0f - uv.V / 1024.0f); + return new Vector2(uv.U / 1024.0f, uv.V / 1024.0f); } private static Vector3 ToNumericsVector3(Common.Vector3 position) diff --git a/NResUI/Rendering/Viewport/Msh/WeaMaterialLibrary.cs b/NResUI/Rendering/Viewport/Msh/WeaMaterialLibrary.cs index 84053e5..05c4419 100644 --- a/NResUI/Rendering/Viewport/Msh/WeaMaterialLibrary.cs +++ b/NResUI/Rendering/Viewport/Msh/WeaMaterialLibrary.cs @@ -46,9 +46,9 @@ public sealed class WeaMaterialLibrary foreach (var materialRef in materialRefs) { - var texture = TryLoadMaterialTexture(gl, materialFs, parseResult.Archive, materialRef.Name); + var texture = TryLoadMaterialTexture(gl, materialFs, parseResult.Archive, materialRef.Name, out var texm); result[materialRef.Id] = texture != null - ? new ViewportMaterial(materialRef.Name, texture.Value) + ? new ViewportMaterial(materialRef.Name, texture.Value, texm) : new ViewportMaterial(materialRef.Name); } @@ -194,9 +194,10 @@ public sealed class WeaMaterialLibrary } private static uint? TryLoadMaterialTexture( - GL gl, FileStream materialFs, NResArchive materialArchive, string materialName + GL gl, FileStream materialFs, NResArchive materialArchive, string materialName, out TexmFile? texm ) { + texm = null; var entry = FindMaterialEntry(materialArchive, materialName); if (entry == null) return null; @@ -235,6 +236,7 @@ public sealed class WeaMaterialLibrary if (texmResult.TexmFile == null) return null; + texm = texmResult.TexmFile; var rgba = texmResult.TexmFile.GetRgba32BytesFromMipmap(0, out var width, out var height); return ViewportTextureLoader.CreateRgbaTexture(gl, rgba, width, height); } diff --git a/NResUI/Rendering/Viewport/ViewportMaterial.cs b/NResUI/Rendering/Viewport/ViewportMaterial.cs index 4ab9546..311b506 100644 --- a/NResUI/Rendering/Viewport/ViewportMaterial.cs +++ b/NResUI/Rendering/Viewport/ViewportMaterial.cs @@ -1,3 +1,5 @@ +using TexmLib; + namespace NResUI.Rendering.Viewport; public sealed class ViewportMaterial @@ -6,10 +8,13 @@ public sealed class ViewportMaterial public uint TextureHandle { get; } public bool HasTexture => TextureHandle != 0; - public ViewportMaterial(string name, uint textureHandle = 0) + public TexmFile? Texm { get; } + + public ViewportMaterial(string name, uint textureHandle = 0, TexmFile? texm = null) { Name = name; TextureHandle = textureHandle; + Texm = texm; } public static ViewportMaterial Untextured { get; } = new("Untextured"); diff --git a/TexmLib/TexmFile.cs b/TexmLib/TexmFile.cs index b83fea2..b61688f 100644 --- a/TexmLib/TexmFile.cs +++ b/TexmLib/TexmFile.cs @@ -277,9 +277,9 @@ public record class TexmFile( // swap endianess back // (rawPixel[0], rawPixel[1], rawPixel[2], rawPixel[3]) = (rawPixel[3], rawPixel[2], rawPixel[1], rawPixel[0]); - var r = rawPixel[0]; + var b = rawPixel[0]; var g = rawPixel[1]; - var b = rawPixel[2]; + var r = rawPixel[2]; var a = rawPixel[3]; result[i + 0] = r;