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