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