diff --git a/Common/Sphere.cs b/Common/Sphere.cs new file mode 100644 index 0000000..b934412 --- /dev/null +++ b/Common/Sphere.cs @@ -0,0 +1,3 @@ +namespace Common; + +public record Sphere(float CenterX, float CenterY, float CenterZ, float Radius); \ No newline at end of file diff --git a/ParkanPlayground/Msh0x01.cs b/ParkanPlayground/Msh0x01.cs index 6d3c91e..4ebd15c 100644 --- a/ParkanPlayground/Msh0x01.cs +++ b/ParkanPlayground/Msh0x01.cs @@ -6,6 +6,7 @@ namespace ParkanPlayground; /// /// MSH-компонент 0x01: таблица узлов модели. /// +/// У ландшафта magic1 - grid_x_count public static class Msh0x01 { public static Msh0x01Component ReadComponent(FileStream mshFs, NResArchive archive) @@ -50,7 +51,7 @@ public static class Msh0x01 elements.Add(new Node( rawBytes, - BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(baseOffset)), + (NodeFlags)BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(baseOffset)), BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(baseOffset + 2)), BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(baseOffset + 4)), BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(baseOffset + 6)), @@ -67,23 +68,28 @@ public static class Msh0x01 /// Узел 0x01. /// Сырые байты узла (length = attr3). Нужны для copy-through редких вариантов, например attr3 = 24. - /// [0x00..0x02] Заголовочное слово узла - /// [0x02..0x04] Индекс родителя или связанного узла + /// [0x00..0x02] Флаги узла + /// [0x02..0x04] Индекс родителя или связанного узла /// [0x04..0x06] Начало блока в карте анимации Msh0x13 или 0xFFFF /// [0x06..0x08] Индекс fallback-ключа в пуле ключей Msh0x08 - /// [0x08..0x26] Индексы slot в Msh0x02 по формуле lod * 5 + group + /// [0x08..0x26] Индексы slot в Msh0x02 по формуле lod * 5 + group public record Node( byte[] RawBytes, - ushort Header0, - ushort ParentOrLink, + NodeFlags Flags, + ushort ParentOrLink_possibly_0x02_index, ushort AnimMapStart, ushort FallbackKey, - ushort[] SlotIndex) + ushort[] Msh02SlotIndicesByLodAndGroupSlotIndex) { public ushort ResolveSlotIndex(int lod, int group = 0) { var index = lod * 5 + group; - return index >= 0 && index < SlotIndex.Length ? SlotIndex[index] : ushort.MaxValue; + return index >= 0 && index < Msh02SlotIndicesByLodAndGroupSlotIndex.Length ? Msh02SlotIndicesByLodAndGroupSlotIndex[index] : ushort.MaxValue; } } } + +public enum NodeFlags +{ + MSH01_NODE_FLAG_NO_SHADOW = 0x40 +} \ No newline at end of file diff --git a/ParkanPlayground/Msh0x02.cs b/ParkanPlayground/Msh0x02.cs index 17c318f..3757240 100644 --- a/ParkanPlayground/Msh0x02.cs +++ b/ParkanPlayground/Msh0x02.cs @@ -100,44 +100,49 @@ public static class Msh0x02 var elements = new List(); var skippedHeader = data.AsSpan(0x8c); - var slotCount = skippedHeader.Length / 68; + var slotCount = skippedHeader.Length / 0x44; + for (var i = 0; i < slotCount; i++) { - var baseOffset = 68 * i; - var opaque = new uint[5]; + var baseOffset = 0x44 * i; + + var opaque = new uint[3]; for (var opaqueIndex = 0; opaqueIndex < opaque.Length; opaqueIndex++) { opaque[opaqueIndex] = - BinaryPrimitives.ReadUInt32LittleEndian(skippedHeader.Slice(baseOffset + 48 + opaqueIndex * 4)); + BinaryPrimitives.ReadUInt32LittleEndian( + skippedHeader.Slice(baseOffset + 0x38 + opaqueIndex * 4) + ); } elements.Add(new Slot( - BinaryPrimitives.ReadUInt16LittleEndian(skippedHeader.Slice(baseOffset + 0)), - BinaryPrimitives.ReadUInt16LittleEndian(skippedHeader.Slice(baseOffset + 2)), - BinaryPrimitives.ReadUInt16LittleEndian(skippedHeader.Slice(baseOffset + 4)), - BinaryPrimitives.ReadUInt16LittleEndian(skippedHeader.Slice(baseOffset + 6)), + BinaryPrimitives.ReadUInt16LittleEndian(skippedHeader.Slice(baseOffset + 0x00)), + BinaryPrimitives.ReadUInt16LittleEndian(skippedHeader.Slice(baseOffset + 0x02)), + BinaryPrimitives.ReadUInt16LittleEndian(skippedHeader.Slice(baseOffset + 0x04)), + BinaryPrimitives.ReadUInt16LittleEndian(skippedHeader.Slice(baseOffset + 0x06)), new Vector3( - BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 8)), - BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 12)), - BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 16)) + BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 0x08)), + BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 0x0c)), + BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 0x10)) ), new Vector3( - BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 20)), - BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 24)), - BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 28)) + BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 0x14)), + BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 0x18)), + BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 0x1c)) ), - new Vector3( - BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 32)), - BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 36)), - BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 40)) - ), - BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 44)), - opaque)); - + new Sphere( + BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 0x20)), + BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 0x24)), + BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 0x28)), + BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 0x2c))), + BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 0x30)), + BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 0x34)), + opaque + )); } return new Msh0x02Component( - new Msh02Header(bb, center, centerW, bottom, top, xyRadius), + new Msh02Header(bb, new Sphere(center.X, center.Y, center.Z, centerW), bottom, top, xyRadius), elements); } @@ -148,38 +153,38 @@ public static class Msh0x02 /// Заголовок 0x02 (length = 0x8C). /// [0x00..0x60] Bounding box из 8 точек. - /// [0x60..0x6C] Центральная точка. - /// [0x6C..0x70] W-компонента центра. + /// [0x60..0x70] Bounding sphere. /// [0x70..0x7C] Нижняя точка. /// [0x7C..0x88] Верхняя точка. /// [0x88..0x8C] Радиус в плоскости XY. public record class Msh02Header( BoundingBox BoundingBox, - Vector3 Center, - float CenterW, + Sphere BoundingSphere, Vector3 Bottom, Vector3 Top, float XYRadius); - /// Slot 0x02 (length = 0x44). + /// Geometry Slot 0x02 (length = 0x44). /// [0x00..0x02] Первый triangle descriptor в Msh0x07. Для terrain-гипотезы может быть диапазоном Msh0x15. /// [0x02..0x04] Количество triangle descriptor в Msh0x07. Для terrain-гипотезы может быть count для Msh0x15. /// [0x04..0x06] Первый batch в таблице 0x0D. /// [0x06..0x08] Количество batch в таблице 0x0D /// [0x08..0x14] Минимум локального AABB /// [0x14..0x20] Максимум локального AABB - /// [0x20..0x2C] Центр bounding sphere - /// [0x2C..0x30] Радиус bounding sphere - /// [0x30..0x44] Пять opaque dword - public record class Slot( + /// [0x20..0x30] Bounding sphere + /// [0x30..0x34] Базовая XY-площадь / footprint area до масштабирования. + /// [0x34..0x38] Базовый объём до масштабирования. + /// [0x38..0x44] 3 opaque dword + public record Slot( ushort TriStart, ushort TriCount, ushort BatchStart, ushort BatchCount, Vector3 LocalMinimum, Vector3 LocalMaximum, - Vector3 Center, - float SphereRadius, + Sphere BoundingSphere, + float BaseXyArea, + float BaseVolume, uint[] Opaque); /// Bounding box заголовка: 8 точек по 3 float (length = 0x60). @@ -191,7 +196,7 @@ public static class Msh0x02 /// [0x3C..0x48] Верхняя передняя правая точка. /// [0x48..0x54] Верхняя задняя правая точка. /// [0x54..0x60] Верхняя задняя левая точка. - public record class BoundingBox( + public record BoundingBox( Vector3 BottomFrontLeft, Vector3 BottomFrontRight, Vector3 BottomBackRight, diff --git a/ParkanPlayground/Msh0x15.cs b/ParkanPlayground/Msh0x15.cs index 3e7c265..5f390e1 100644 --- a/ParkanPlayground/Msh0x15.cs +++ b/ParkanPlayground/Msh0x15.cs @@ -18,32 +18,36 @@ public static class Msh0x15 throw new Exception("Archive doesn't contain file (15)"); } - if (entry.ElementSize != 28) + if (entry.ElementSize != 0x1C) { throw new Exception("Terrain triangle component (0x15) element size is not 28"); } - if (entry.FileLength % entry.ElementSize != 0) + if (entry.FileLength % entry.ElementSize != 0x0) { throw new Exception("Terrain triangle component (0x15) payload size is not divisible by element size"); } var data = new byte[entry.FileLength]; mshFs.Seek(entry.OffsetInFile, SeekOrigin.Begin); - mshFs.ReadExactly(data, 0, data.Length); + mshFs.ReadExactly(data, 0x0, data.Length); - var elementBytes = data.Chunk(28); + var elementBytes = data.Chunk(0x1C); var elements = elementBytes.Select(x => new TerrainTriangle( - BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(0)), - BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(4)), - BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(8)), - BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(10)), - BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(12)), - BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(14)), - BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(18)), - BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(22)), - BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(26)))).ToList(); + Flags: (TerrainTriangleFlags)BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(0x0)), + MaterialData: BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(0x4)), + Vertex1Index: BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0x8)), + Vertex2Index: BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0xA)), + Vertex3Index: BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0xC)), + Neighbor0: BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0x0E)), + Neighbor1: BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0x10)), + Neighbor2: BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0x12)), + NormalX: BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0x14)), + NormalY: BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0x16)), + NormalZ: BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0x18)), + PackedEdgeOrSelector: BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0x1A)))) + .ToList(); return elements; } @@ -54,20 +58,32 @@ public static class Msh0x15 /// [0x08..0x0A] Индекс первой вершины в position stream Msh0x03 /// [0x0A..0x0C] Индекс второй вершины в position stream Msh0x03 /// [0x0C..0x0E] Индекс третьей вершины в position stream Msh0x03 - /// [0x0E..0x12] Opaque поле - /// [0x12..0x16] Opaque поле - /// [0x16..0x1A] Opaque поле - /// [0x1A..0x1C] Opaque поле + /// [0x0E..0x10] Сосед 0 + /// [0x10..0x12] Сосед 1 + /// [0x12..0x14] Сосед 2 + /// [0x14..0x16] Направление нормали + /// [0x16..0x18] Направление нормали + /// [0x18..0x1A] Направление нормали + /// [0x1A..0x1C] TODO public readonly record struct TerrainTriangle( - uint Flags, + TerrainTriangleFlags Flags, uint MaterialData, ushort Vertex1Index, ushort Vertex2Index, ushort Vertex3Index, - uint Opaque0E, - uint Opaque12, - uint Opaque16, - ushort Opaque1A); + ushort Neighbor0, + ushort Neighbor1, + ushort Neighbor2, + ushort NormalX, + ushort NormalY, + ushort NormalZ, + ushort PackedEdgeOrSelector); } + +public enum TerrainTriangleFlags { + MSH15_FLAG_REFLECTIVE_SURFACE = 0x20000, + MSH15_FLAG_HAS_MICROTEXTURE = 0x400, + MSH15_FLAG_DISABLE_BACKFACE_TEST = 0x8 +} \ No newline at end of file