diff --git a/Common/Quaternion.cs b/Common/Quaternion.cs new file mode 100644 index 0000000..9f93a67 --- /dev/null +++ b/Common/Quaternion.cs @@ -0,0 +1,16 @@ +namespace Common; + +public record Quaternion(float X, float Y, float Z, float W); + +public record UShortQuaternion(ushort X, ushort Y, ushort Z, ushort W) +{ + public Quaternion ToRegular() + { + return new Quaternion( + X / 32767f, + Y / 32767f, + Z / 32767f, + W / 32767f + ); + } +}; \ No newline at end of file diff --git a/ParkanPlayground/Msh0x02.cs b/ParkanPlayground/Msh0x02.cs index 3f4da1d..3ef3d1d 100644 --- a/ParkanPlayground/Msh0x02.cs +++ b/ParkanPlayground/Msh0x02.cs @@ -82,8 +82,8 @@ public static class Msh0x02 return new Msh02Header( BoundingBox: bbox, BoundingSphere: ReadSphere(header, 0x60), - Bottom: ReadVector3(header, 0x70), - Top: ReadVector3(header, 0x7C), + MeshStart: ReadVector3(header, 0x70), + MeshEnd: ReadVector3(header, 0x7C), XyRadius: BinaryPrimitives.ReadSingleLittleEndian(header.Slice(0x88, 4))); } @@ -120,14 +120,14 @@ public static class Msh0x02 /// Заголовок MSH 0x02, length = 0x8C. /// [0x00..0x60] Bounding box из 8 точек. /// [0x60..0x70] Bounding sphere: xyz = center, w = radius. - /// [0x70..0x7C] Нижняя/опорная точка меша. - /// [0x7C..0x88] Верхняя точка меша. + /// [0x70..0x7C] Нижняя/опорная точка меша. Минимальная точка меша ?? + /// [0x7C..0x88] Верхняя точка меша. Максимальная точка меша ?? /// [0x88..0x8C] Радиус/extent в плоскости XY. public sealed record Msh02Header( BoundingBox BoundingBox, Sphere BoundingSphere, - Vector3 Bottom, - Vector3 Top, + Vector3 MeshStart, + Vector3 MeshEnd, float XyRadius); /// Geometry slot MSH 0x02, length = 0x44. diff --git a/ParkanPlayground/Msh0x05.cs b/ParkanPlayground/Msh0x05.cs index 90a893e..35c2b95 100644 --- a/ParkanPlayground/Msh0x05.cs +++ b/ParkanPlayground/Msh0x05.cs @@ -47,5 +47,4 @@ public static class Msh0x05 /// Упакованные UV0: две int16-компоненты (length = 4). /// [0x00..0x02] U-компонента, uv = U / 1024.0. /// [0x02..0x04] V-компонента, uv = V / 1024.0. -public readonly record struct Msh05Uv(short U, short V) -; +public readonly record struct Msh05Uv(short U, short V); diff --git a/ParkanPlayground/Msh0x06.cs b/ParkanPlayground/Msh0x06.cs index a87c986..a04b299 100644 --- a/ParkanPlayground/Msh0x06.cs +++ b/ParkanPlayground/Msh0x06.cs @@ -54,7 +54,8 @@ public static class Msh0x06 public static IEnumerable EnumerateTriangles( IReadOnlyList indices, int indexStart, - int indexCount) + int indexCount + ) { if (indexStart < 0) { @@ -81,11 +82,12 @@ public static class Msh0x06 } /// Тройка индексов triangle из MSH 0x06. - /// Первый vertex index внутри batch/base vertex range. - /// Второй vertex index внутри batch/base vertex range. - /// Третий vertex index внутри batch/base vertex range. + /// Первый vertex index внутри batch/base vertex range. + /// Второй vertex index внутри batch/base vertex range. + /// Третий vertex index внутри batch/base vertex range. public readonly record struct TriangleIndices( - ushort A, - ushort B, - ushort C); -} \ No newline at end of file + ushort Vertex0, + ushort Vertex1, + ushort Vertex2 + ); +} diff --git a/ParkanPlayground/Msh0x07.cs b/ParkanPlayground/Msh0x07.cs index 399e250..325fa65 100644 --- a/ParkanPlayground/Msh0x07.cs +++ b/ParkanPlayground/Msh0x07.cs @@ -47,13 +47,13 @@ public static class Msh0x07 descriptors.Add(new TriangleDescriptor( Flags: (TriangleFlags)BinaryPrimitives.ReadUInt16LittleEndian(element.Slice(0x00, 2)), - Link0: BinaryPrimitives.ReadUInt16LittleEndian(element.Slice(0x02, 2)), - Link1: BinaryPrimitives.ReadUInt16LittleEndian(element.Slice(0x04, 2)), - Link2: BinaryPrimitives.ReadUInt16LittleEndian(element.Slice(0x06, 2)), + LinkedTriangleIndex0_0x07: BinaryPrimitives.ReadUInt16LittleEndian(element.Slice(0x02, 2)), + LinkedTriangleIndex1_0x07: BinaryPrimitives.ReadUInt16LittleEndian(element.Slice(0x04, 2)), + LinkedTriangleIndex2_0x07: BinaryPrimitives.ReadUInt16LittleEndian(element.Slice(0x06, 2)), PackedNormalX: BinaryPrimitives.ReadInt16LittleEndian(element.Slice(0x08, 2)), PackedNormalY: BinaryPrimitives.ReadInt16LittleEndian(element.Slice(0x0A, 2)), PackedNormalZ: BinaryPrimitives.ReadInt16LittleEndian(element.Slice(0x0C, 2)), - SelectorPacked: BinaryPrimitives.ReadUInt16LittleEndian(element.Slice(0x0E, 2)))); + PackedSelector: BinaryPrimitives.ReadUInt16LittleEndian(element.Slice(0x0E, 2)))); } return descriptors; @@ -61,22 +61,22 @@ public static class Msh0x07 /// Описатель triangle MSH 0x07, length = 0x10. /// [0x00..0x02] Triangle flags. Используются GeometryWalkFilter require/exclude. - /// [0x02..0x04] Указывает на треугольники в 0x07 (текущем) компоненте. - /// [0x04..0x06] Указывает на треугольники в 0x07 (текущем) компоненте. - /// [0x06..0x08] Указывает на треугольники в 0x07 (текущем) компоненте. + /// [0x02..0x04] Указывает на треугольники в 0x07 (текущем) компоненте. + /// [0x04..0x06] Указывает на треугольники в 0x07 (текущем) компоненте. + /// [0x06..0x08] Указывает на треугольники в 0x07 (текущем) компоненте. /// [0x08..0x0A] Packed normal X, int16, scale = 1 / 32767. /// [0x0A..0x0C] Packed normal Y, int16, scale = 1 / 32767. /// [0x0C..0x0E] Packed normal Z, int16, scale = 1 / 32767. - /// [0x0E..0x10] Packed selectors. 3 трактуется как 0xFFFF. + /// [0x0E..0x10] Packed selectors. 3 трактуется как 0xFFFF. public readonly record struct TriangleDescriptor( TriangleFlags Flags, - ushort Link0, - ushort Link1, - ushort Link2, + ushort LinkedTriangleIndex0_0x07, + ushort LinkedTriangleIndex1_0x07, + ushort LinkedTriangleIndex2_0x07, short PackedNormalX, short PackedNormalY, short PackedNormalZ, - ushort SelectorPacked) + ushort PackedSelector) { public Vector3 Normal => new( PackedNormalX * PackedNormalScale, @@ -90,7 +90,7 @@ public static class Msh0x07 throw new ArgumentOutOfRangeException(nameof(index)); } - var selector = (SelectorPacked >> (index * 2)) & 0b11; + var selector = (PackedSelector >> (index * 2)) & 0b11; return selector == 3 ? ushort.MaxValue diff --git a/ParkanPlayground/Msh0x08.cs b/ParkanPlayground/Msh0x08.cs new file mode 100644 index 0000000..7666853 --- /dev/null +++ b/ParkanPlayground/Msh0x08.cs @@ -0,0 +1,46 @@ +using System.Buffers.Binary; +using Common; +using NResLib; + +namespace ParkanPlayground; + +public static class Msh0x08 +{ + public static List ReadComponent(FileStream mshFs, NResArchive archive) + { + var entry = archive.Files.FirstOrDefault(x => x.FileType == "08 00 00 00"); + + if (entry is null) + { + throw new Exception("Archive doesn't contain animation descriptor component (0x08)"); + } + + mshFs.Seek(entry.OffsetInFile, SeekOrigin.Begin); + + var descriptors = new List(); + + for (var i = 0; i < entry.ElementCount; i++) + { + descriptors.Add(new AnimationDescriptor( + new Vector3(mshFs.ReadFloatLittleEndian(), + mshFs.ReadFloatLittleEndian(), + mshFs.ReadFloatLittleEndian()), + mshFs.ReadFloatLittleEndian(), + new UShortQuaternion( + mshFs.ReadUInt16LittleEndian(), + mshFs.ReadUInt16LittleEndian(), + mshFs.ReadUInt16LittleEndian(), + mshFs.ReadUInt16LittleEndian() + ) + )); + } + + return descriptors; + } + + public record AnimationDescriptor( + Vector3 Position, + float Time, + UShortQuaternion Rotation + ); +} diff --git a/ParkanPlayground/MshConverter.cs b/ParkanPlayground/MshConverter.cs index e32ad73..352cc08 100644 --- a/ParkanPlayground/MshConverter.cs +++ b/ParkanPlayground/MshConverter.cs @@ -112,75 +112,81 @@ public sealed class MshConverter var skippedBatches = 0; var skippedFaces = 0; - for (var pieceIndex = 0; pieceIndex < nodes.Nodes.Count; pieceIndex++) + for (var pieceIndex = 1; pieceIndex < nodes.Nodes.Count; pieceIndex += 100000) { var node = nodes.Nodes[pieceIndex]; - var slotIndex = node.ResolveSlotIndex(lod, group); - if (slotIndex == ushort.MaxValue) + for (var s = 0; s < node.Msh02SlotIndicesByStateAndLOD.Length; s++) { - skippedSlots++; - continue; - } + var slotIndex = node.Msh02SlotIndicesByStateAndLOD[s]; - if (slotIndex >= geometry.Slots.Count) - { - Warn($"Piece {pieceIndex}: geometry slot {slotIndex} out of range"); - skippedSlots++; - continue; - } - - var slot = geometry.Slots[slotIndex]; - - if (!slot.HasBatches) - { - continue; - } - - if (slot.BatchEndExclusive0x0D > batches.Count) - { - Warn($"Piece {pieceIndex}: batch range {slot.BatchStart0x0D}:{slot.BatchCount0x0D} out of range"); - skippedBatches++; - continue; - } - - writer.WriteLine(); - writer.WriteLine($"g piece_{pieceIndex}_slot_{slotIndex}"); - - for (var batchIndex = slot.BatchStart0x0D; batchIndex < slot.BatchEndExclusive0x0D; batchIndex++) - { - var batch = batches[batchIndex]; - - if (batch.IndexStart0x06 + batch.IndexCount0x06 > indices.Count) + if (slotIndex == ushort.MaxValue) { - Warn($"Piece {pieceIndex}, batch {batchIndex}: index range {batch.IndexStart0x06}:{batch.IndexCount0x06} out of range"); + skippedSlots++; + continue; + } + + if (slotIndex >= geometry.Slots.Count) + { + Warn($"Piece {pieceIndex}: geometry slot {slotIndex} out of range"); + skippedSlots++; + continue; + } + + var slot = geometry.Slots[slotIndex]; + + if (!slot.HasBatches) + { + continue; + } + + if (slot.BatchEndExclusive0x0D > batches.Count) + { + Warn($"Piece {pieceIndex}: batch range {slot.BatchStart0x0D}:{slot.BatchCount0x0D} out of range"); skippedBatches++; continue; } - writer.WriteLine($"# batch {batchIndex}, material {batch.MaterialIndex}, flags {batch.Flags}"); + writer.WriteLine(); + writer.WriteLine($"o piece_{pieceIndex}_{s}"); - for (var i = 0; i + 2 < batch.IndexCount0x06; i += 3) + for (var batchIndex = slot.BatchStart0x0D; batchIndex < slot.BatchEndExclusive0x0D; batchIndex++) { - var indexBase = (int)batch.IndexStart0x06 + i; + var batch = batches[batchIndex]; - var v1 = checked((int)batch.BaseVertex0x03 + indices[indexBase + 0]); - var v2 = checked((int)batch.BaseVertex0x03 + indices[indexBase + 1]); - var v3 = checked((int)batch.BaseVertex0x03 + indices[indexBase + 2]); - - if (!IsValidTriangle(vertices.Count, v1, v2, v3)) + if (batch.IndexStart0x06 + batch.IndexCount0x06 > indices.Count) { - skippedFaces++; + Warn( + $"Piece {pieceIndex}, batch {batchIndex}: index range {batch.IndexStart0x06}:{batch.IndexCount0x06} out of range"); + skippedBatches++; continue; } - WriteFace(writer, v1, v2, v3); - exportedFaces++; - } + writer.WriteLine($"# batch {batchIndex}, material {batch.MaterialIndex}, flags {batch.Flags}"); - if (batch.IndexCount0x06 % 3 != 0) - { - Warn($"Piece {pieceIndex}, batch {batchIndex}: index count {batch.IndexCount0x06} is not divisible by 3"); + for (var i = 0; i + 2 < batch.IndexCount0x06; i += 3) + { + var indexBase = (int)batch.IndexStart0x06 + i; + + var v1 = checked((int)batch.BaseVertex0x03 + indices[indexBase + 0]); + var v2 = checked((int)batch.BaseVertex0x03 + indices[indexBase + 1]); + var v3 = checked((int)batch.BaseVertex0x03 + indices[indexBase + 2]); + + if (!IsValidTriangle(vertices.Count, v1, v2, v3)) + { + skippedFaces++; + continue; + } + + WriteFace(writer, v1, v2, v3); + exportedFaces++; + } + + if (batch.IndexCount0x06 % 3 != 0) + { + Warn( + $"Piece {pieceIndex}, batch {batchIndex}: index count {batch.IndexCount0x06} is not divisible by 3"); + } } } } diff --git a/ParkanPlayground/Program.cs b/ParkanPlayground/Program.cs index 7c14dd9..283cea7 100644 --- a/ParkanPlayground/Program.cs +++ b/ParkanPlayground/Program.cs @@ -1,3 +1,8 @@ using Common; +using ParkanPlayground; System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance); + +var converter = new MshConverter(); + +converter.Convert("E:\\ParkanUnpacked\\fortif.rlb\\130_fr_b_plant.msh"); \ No newline at end of file diff --git a/README.md b/README.md index 79d23ce..0a1d8e3 100644 --- a/README.md +++ b/README.md @@ -434,6 +434,8 @@ enum EffectTemplateFlags : uint32_t - `0x502` - ResTree - `0x700` - INetWatcher - `0x701` - INetworkInterface +- `0x802` - INetSessionBrowser (придумал сам implemented by CNetManager in services.dll) +- `0x803` - INetManager ## SuperAI = Clan с точки зрения индексации @@ -441,7 +443,7 @@ enum EffectTemplateFlags : uint32_t ## Опции -World3D.dll содержит функцию CreateGameSettings. +World3D.dll содержит singleton Registry в CreateGameSettings. Она создаёт объект настроек и далее вызывает методы в соседних библиотеках. - Terrain.dll - InitializeSettings - Effect.dll - InitializeSettings