refactorings

This commit is contained in:
bird_egop
2026-05-11 05:23:14 +03:00
parent eb458ad9a8
commit 67a4aa84bf
4 changed files with 95 additions and 65 deletions
+3
View File
@@ -0,0 +1,3 @@
namespace Common;
public record Sphere(float CenterX, float CenterY, float CenterZ, float Radius);
+14 -8
View File
@@ -6,6 +6,7 @@ namespace ParkanPlayground;
/// <summary> /// <summary>
/// MSH-компонент 0x01: таблица узлов модели. /// MSH-компонент 0x01: таблица узлов модели.
/// </summary> /// </summary>
/// У ландшафта magic1 - grid_x_count
public static class Msh0x01 public static class Msh0x01
{ {
public static Msh0x01Component ReadComponent(FileStream mshFs, NResArchive archive) public static Msh0x01Component ReadComponent(FileStream mshFs, NResArchive archive)
@@ -50,7 +51,7 @@ public static class Msh0x01
elements.Add(new Node( elements.Add(new Node(
rawBytes, rawBytes,
BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(baseOffset)), (NodeFlags)BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(baseOffset)),
BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(baseOffset + 2)), BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(baseOffset + 2)),
BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(baseOffset + 4)), BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(baseOffset + 4)),
BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(baseOffset + 6)), BinaryPrimitives.ReadUInt16LittleEndian(dataSpan.Slice(baseOffset + 6)),
@@ -67,23 +68,28 @@ public static class Msh0x01
/// <summary>Узел 0x01.</summary> /// <summary>Узел 0x01.</summary>
/// <param name="RawBytes">Сырые байты узла (length = attr3). Нужны для copy-through редких вариантов, например attr3 = 24.</param> /// <param name="RawBytes">Сырые байты узла (length = attr3). Нужны для copy-through редких вариантов, например attr3 = 24.</param>
/// <param name="Header0">[0x00..0x02] Заголовочное слово узла</param> /// <param name="Flags">[0x00..0x02] Флаги узла</param>
/// <param name="ParentOrLink">[0x02..0x04] Индекс родителя или связанного узла</param> /// <param name="ParentOrLink_possibly_0x02_index">[0x02..0x04] Индекс родителя или связанного узла</param>
/// <param name="AnimMapStart">[0x04..0x06] Начало блока в карте анимации Msh0x13 или 0xFFFF</param> /// <param name="AnimMapStart">[0x04..0x06] Начало блока в карте анимации Msh0x13 или 0xFFFF</param>
/// <param name="FallbackKey">[0x06..0x08] Индекс fallback-ключа в пуле ключей Msh0x08</param> /// <param name="FallbackKey">[0x06..0x08] Индекс fallback-ключа в пуле ключей Msh0x08</param>
/// <param name="SlotIndex">[0x08..0x26] Индексы slot в Msh0x02 по формуле lod * 5 + group</param> /// <param name="Msh02SlotIndicesByLodAndGroupSlotIndex">[0x08..0x26] Индексы slot в Msh0x02 по формуле lod * 5 + group</param>
public record Node( public record Node(
byte[] RawBytes, byte[] RawBytes,
ushort Header0, NodeFlags Flags,
ushort ParentOrLink, ushort ParentOrLink_possibly_0x02_index,
ushort AnimMapStart, ushort AnimMapStart,
ushort FallbackKey, ushort FallbackKey,
ushort[] SlotIndex) ushort[] Msh02SlotIndicesByLodAndGroupSlotIndex)
{ {
public ushort ResolveSlotIndex(int lod, int group = 0) public ushort ResolveSlotIndex(int lod, int group = 0)
{ {
var index = lod * 5 + group; 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
}
+40 -35
View File
@@ -100,44 +100,49 @@ public static class Msh0x02
var elements = new List<Slot>(); var elements = new List<Slot>();
var skippedHeader = data.AsSpan(0x8c); var skippedHeader = data.AsSpan(0x8c);
var slotCount = skippedHeader.Length / 68; var slotCount = skippedHeader.Length / 0x44;
for (var i = 0; i < slotCount; i++) for (var i = 0; i < slotCount; i++)
{ {
var baseOffset = 68 * i; var baseOffset = 0x44 * i;
var opaque = new uint[5];
var opaque = new uint[3];
for (var opaqueIndex = 0; opaqueIndex < opaque.Length; opaqueIndex++) for (var opaqueIndex = 0; opaqueIndex < opaque.Length; opaqueIndex++)
{ {
opaque[opaqueIndex] = opaque[opaqueIndex] =
BinaryPrimitives.ReadUInt32LittleEndian(skippedHeader.Slice(baseOffset + 48 + opaqueIndex * 4)); BinaryPrimitives.ReadUInt32LittleEndian(
skippedHeader.Slice(baseOffset + 0x38 + opaqueIndex * 4)
);
} }
elements.Add(new Slot( elements.Add(new Slot(
BinaryPrimitives.ReadUInt16LittleEndian(skippedHeader.Slice(baseOffset + 0)), BinaryPrimitives.ReadUInt16LittleEndian(skippedHeader.Slice(baseOffset + 0x00)),
BinaryPrimitives.ReadUInt16LittleEndian(skippedHeader.Slice(baseOffset + 2)), BinaryPrimitives.ReadUInt16LittleEndian(skippedHeader.Slice(baseOffset + 0x02)),
BinaryPrimitives.ReadUInt16LittleEndian(skippedHeader.Slice(baseOffset + 4)), BinaryPrimitives.ReadUInt16LittleEndian(skippedHeader.Slice(baseOffset + 0x04)),
BinaryPrimitives.ReadUInt16LittleEndian(skippedHeader.Slice(baseOffset + 6)), BinaryPrimitives.ReadUInt16LittleEndian(skippedHeader.Slice(baseOffset + 0x06)),
new Vector3( new Vector3(
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 8)), BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 0x08)),
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 12)), BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 0x0c)),
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 16)) BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 0x10))
), ),
new Vector3( new Vector3(
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 20)), BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 0x14)),
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 24)), BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 0x18)),
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 28)) BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 0x1c))
), ),
new Vector3( new Sphere(
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 32)), BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 0x20)),
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 36)), BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 0x24)),
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 40)) BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 0x28)),
), BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 0x2c))),
BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 44)), BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 0x30)),
opaque)); BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 0x34)),
opaque
));
} }
return new Msh0x02Component( 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); elements);
} }
@@ -148,38 +153,38 @@ public static class Msh0x02
/// <summary>Заголовок 0x02 (length = 0x8C).</summary> /// <summary>Заголовок 0x02 (length = 0x8C).</summary>
/// <param name="BoundingBox">[0x00..0x60] Bounding box из 8 точек.</param> /// <param name="BoundingBox">[0x00..0x60] Bounding box из 8 точек.</param>
/// <param name="Center">[0x60..0x6C] Центральная точка.</param> /// <param name="BoundingSphere">[0x60..0x70] Bounding sphere.</param>
/// <param name="CenterW">[0x6C..0x70] W-компонента центра.</param>
/// <param name="Bottom">[0x70..0x7C] Нижняя точка.</param> /// <param name="Bottom">[0x70..0x7C] Нижняя точка.</param>
/// <param name="Top">[0x7C..0x88] Верхняя точка.</param> /// <param name="Top">[0x7C..0x88] Верхняя точка.</param>
/// <param name="XYRadius">[0x88..0x8C] Радиус в плоскости XY.</param> /// <param name="XYRadius">[0x88..0x8C] Радиус в плоскости XY.</param>
public record class Msh02Header( public record class Msh02Header(
BoundingBox BoundingBox, BoundingBox BoundingBox,
Vector3 Center, Sphere BoundingSphere,
float CenterW,
Vector3 Bottom, Vector3 Bottom,
Vector3 Top, Vector3 Top,
float XYRadius); float XYRadius);
/// <summary>Slot 0x02 (length = 0x44).</summary> /// <summary>Geometry Slot 0x02 (length = 0x44).</summary>
/// <param name="TriStart">[0x00..0x02] Первый triangle descriptor в Msh0x07. Для terrain-гипотезы может быть диапазоном Msh0x15.</param> /// <param name="TriStart">[0x00..0x02] Первый triangle descriptor в Msh0x07. Для terrain-гипотезы может быть диапазоном Msh0x15.</param>
/// <param name="TriCount">[0x02..0x04] Количество triangle descriptor в Msh0x07. Для terrain-гипотезы может быть count для Msh0x15.</param> /// <param name="TriCount">[0x02..0x04] Количество triangle descriptor в Msh0x07. Для terrain-гипотезы может быть count для Msh0x15.</param>
/// <param name="BatchStart">[0x04..0x06] Первый batch в таблице 0x0D.</param> /// <param name="BatchStart">[0x04..0x06] Первый batch в таблице 0x0D.</param>
/// <param name="BatchCount">[0x06..0x08] Количество batch в таблице 0x0D</param> /// <param name="BatchCount">[0x06..0x08] Количество batch в таблице 0x0D</param>
/// <param name="LocalMinimum">[0x08..0x14] Минимум локального AABB</param> /// <param name="LocalMinimum">[0x08..0x14] Минимум локального AABB</param>
/// <param name="LocalMaximum">[0x14..0x20] Максимум локального AABB</param> /// <param name="LocalMaximum">[0x14..0x20] Максимум локального AABB</param>
/// <param name="Center">[0x20..0x2C] Центр bounding sphere</param> /// <param name="BoundingSphere">[0x20..0x30] Bounding sphere</param>
/// <param name="SphereRadius">[0x2C..0x30] Радиус bounding sphere</param> /// <param name="BaseXyArea">[0x30..0x34] Базовая XY-площадь / footprint area до масштабирования.</param>
/// <param name="Opaque">[0x30..0x44] Пять opaque dword</param> /// <param name="BaseVolume">[0x34..0x38] Базовый объём до масштабирования.</param>
public record class Slot( /// <param name="Opaque">[0x38..0x44] 3 opaque dword</param>
public record Slot(
ushort TriStart, ushort TriStart,
ushort TriCount, ushort TriCount,
ushort BatchStart, ushort BatchStart,
ushort BatchCount, ushort BatchCount,
Vector3 LocalMinimum, Vector3 LocalMinimum,
Vector3 LocalMaximum, Vector3 LocalMaximum,
Vector3 Center, Sphere BoundingSphere,
float SphereRadius, float BaseXyArea,
float BaseVolume,
uint[] Opaque); uint[] Opaque);
/// <summary>Bounding box заголовка: 8 точек по 3 float (length = 0x60).</summary> /// <summary>Bounding box заголовка: 8 точек по 3 float (length = 0x60).</summary>
@@ -191,7 +196,7 @@ public static class Msh0x02
/// <param name="TopFrontRight">[0x3C..0x48] Верхняя передняя правая точка.</param> /// <param name="TopFrontRight">[0x3C..0x48] Верхняя передняя правая точка.</param>
/// <param name="TopBackRight">[0x48..0x54] Верхняя задняя правая точка.</param> /// <param name="TopBackRight">[0x48..0x54] Верхняя задняя правая точка.</param>
/// <param name="TopBackLeft">[0x54..0x60] Верхняя задняя левая точка.</param> /// <param name="TopBackLeft">[0x54..0x60] Верхняя задняя левая точка.</param>
public record class BoundingBox( public record BoundingBox(
Vector3 BottomFrontLeft, Vector3 BottomFrontLeft,
Vector3 BottomFrontRight, Vector3 BottomFrontRight,
Vector3 BottomBackRight, Vector3 BottomBackRight,
+38 -22
View File
@@ -18,32 +18,36 @@ public static class Msh0x15
throw new Exception("Archive doesn't contain file (15)"); 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"); 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"); throw new Exception("Terrain triangle component (0x15) payload size is not divisible by element size");
} }
var data = new byte[entry.FileLength]; var data = new byte[entry.FileLength];
mshFs.Seek(entry.OffsetInFile, SeekOrigin.Begin); 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( var elements = elementBytes.Select(x => new TerrainTriangle(
BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(0)), Flags: (TerrainTriangleFlags)BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(0x0)),
BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(4)), MaterialData: BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(0x4)),
BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(8)), Vertex1Index: BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0x8)),
BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(10)), Vertex2Index: BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0xA)),
BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(12)), Vertex3Index: BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0xC)),
BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(14)), Neighbor0: BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0x0E)),
BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(18)), Neighbor1: BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0x10)),
BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(22)), Neighbor2: BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0x12)),
BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(26)))).ToList(); 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; return elements;
} }
@@ -54,20 +58,32 @@ public static class Msh0x15
/// <param name="Vertex1Index">[0x08..0x0A] Индекс первой вершины в position stream Msh0x03</param> /// <param name="Vertex1Index">[0x08..0x0A] Индекс первой вершины в position stream Msh0x03</param>
/// <param name="Vertex2Index">[0x0A..0x0C] Индекс второй вершины в position stream Msh0x03</param> /// <param name="Vertex2Index">[0x0A..0x0C] Индекс второй вершины в position stream Msh0x03</param>
/// <param name="Vertex3Index">[0x0C..0x0E] Индекс третьей вершины в position stream Msh0x03</param> /// <param name="Vertex3Index">[0x0C..0x0E] Индекс третьей вершины в position stream Msh0x03</param>
/// <param name="Opaque0E">[0x0E..0x12] Opaque поле</param> /// <param name="Neighbor0">[0x0E..0x10] Сосед 0</param>
/// <param name="Opaque12">[0x12..0x16] Opaque поле</param> /// <param name="Neighbor1">[0x10..0x12] Сосед 1</param>
/// <param name="Opaque16">[0x16..0x1A] Opaque поле</param> /// <param name="Neighbor2">[0x12..0x14] Сосед 2</param>
/// <param name="Opaque1A">[0x1A..0x1C] Opaque поле</param> /// <param name="NormalX">[0x14..0x16] Направление нормали</param>
/// <param name="NormalY">[0x16..0x18] Направление нормали</param>
/// <param name="NormalZ">[0x18..0x1A] Направление нормали</param>
/// <param name="PackedEdgeOrSelector">[0x1A..0x1C] TODO</param>
public readonly record struct TerrainTriangle( public readonly record struct TerrainTriangle(
uint Flags, TerrainTriangleFlags Flags,
uint MaterialData, uint MaterialData,
ushort Vertex1Index, ushort Vertex1Index,
ushort Vertex2Index, ushort Vertex2Index,
ushort Vertex3Index, ushort Vertex3Index,
uint Opaque0E, ushort Neighbor0,
uint Opaque12, ushort Neighbor1,
uint Opaque16, ushort Neighbor2,
ushort Opaque1A); 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
} }