2026-05-09 20:36:43 +03:00
|
|
|
|
using System.Buffers.Binary;
|
|
|
|
|
|
using NResLib;
|
|
|
|
|
|
|
2026-05-17 15:40:16 +03:00
|
|
|
|
namespace MshLib;
|
2026-05-09 20:36:43 +03:00
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2026-05-12 01:19:19 +03:00
|
|
|
|
/// MSH-компонент 0x0D: таблица render/intersection batches.
|
|
|
|
|
|
/// Используется через MSH_02_geometry_slot.batch_start_0x0d / batch_count_0x0d.
|
2026-05-09 20:36:43 +03:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
public static class Msh0x0D
|
|
|
|
|
|
{
|
|
|
|
|
|
public const int ElementSize = 20;
|
2026-05-12 01:19:19 +03:00
|
|
|
|
|
|
|
|
|
|
public static List<Batch> ReadComponent(FileStream mshFs, NResArchive archive)
|
2026-05-09 20:36:43 +03:00
|
|
|
|
{
|
|
|
|
|
|
var entry = archive.Files.FirstOrDefault(x => x.FileType == "0D 00 00 00");
|
|
|
|
|
|
|
|
|
|
|
|
if (entry is null)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new Exception("Archive doesn't contain file (0D)");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (entry.ElementSize != ElementSize)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new Exception("Batch table component (0x0D) element size is not 20");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (entry.FileLength % entry.ElementSize != 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
throw new Exception("Batch table component (0x0D) payload size is not divisible by element size");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var data = new byte[entry.FileLength];
|
2026-05-12 01:19:19 +03:00
|
|
|
|
|
2026-05-09 20:36:43 +03:00
|
|
|
|
mshFs.Seek(entry.OffsetInFile, SeekOrigin.Begin);
|
|
|
|
|
|
mshFs.ReadExactly(data, 0, data.Length);
|
|
|
|
|
|
|
2026-05-12 01:19:19 +03:00
|
|
|
|
return data
|
|
|
|
|
|
.Chunk(ElementSize)
|
|
|
|
|
|
.Select(x => new Batch(
|
2026-06-06 23:25:14 +03:00
|
|
|
|
Flags: (BatchFlags)BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0x00)),
|
|
|
|
|
|
MaterialIndexHi: BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0x02)),
|
|
|
|
|
|
MaterialIndexLo: x.AsSpan(0x04)[0],
|
|
|
|
|
|
LightmapIndex: x.AsSpan(0x05)[0],
|
|
|
|
|
|
LocalBatchIndex: BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0x06)),
|
|
|
|
|
|
IndexCount0x06: BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0x08)),
|
|
|
|
|
|
IndexStart0x06: BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(0x0A)),
|
|
|
|
|
|
VertexCount0x03: BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0x0E)),
|
|
|
|
|
|
BaseVertex0x03: BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0x10))
|
|
|
|
|
|
)
|
|
|
|
|
|
)
|
2026-05-12 01:19:19 +03:00
|
|
|
|
.ToList();
|
2026-05-09 20:36:43 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-12 01:19:19 +03:00
|
|
|
|
/// <summary>MSH batch 0x0D, sizeof 0x14.</summary>
|
|
|
|
|
|
/// <param name="Flags">[0x00..0x02] Флаги batch.</param>
|
2026-06-06 23:25:14 +03:00
|
|
|
|
/// <param name="MaterialIndexHi">[0x02..0x04] Индекс material slot. Может быть overridden CAniMesh::forced_material_index.</param>
|
|
|
|
|
|
/// <param name="MaterialIndexLo">[0x04..0x05] Индекс материала</param>
|
|
|
|
|
|
/// <param name="LightmapIndex">[0x05..0x06] Индекс карты освещения</param>
|
2026-05-12 01:19:19 +03:00
|
|
|
|
/// <param name="LocalBatchIndex">[0x06..0x08] Локальный batch index. В GetBatchRenderData складывается с MSH_piece.local_index_base / batch base.</param>
|
2026-05-13 02:53:34 +03:00
|
|
|
|
/// <param name="IndexCount0x06">[0x08..0x0A] Количество индексов из component 0x06.</param>
|
|
|
|
|
|
/// <param name="IndexStart0x06">[0x0A..0x0E] Первый индекс в component 0x06.</param>
|
|
|
|
|
|
/// <param name="VertexCount0x03">[0x0E..0x10] Количество вершин для render primitive.</param>
|
|
|
|
|
|
/// <param name="BaseVertex0x03">[0x10..0x14] Base vertex в vertex streams, включая position stream 0x03.</param>
|
2026-05-09 20:36:43 +03:00
|
|
|
|
public readonly record struct Batch(
|
2026-05-12 01:19:19 +03:00
|
|
|
|
BatchFlags Flags,
|
2026-06-06 23:25:14 +03:00
|
|
|
|
ushort MaterialIndexHi,
|
|
|
|
|
|
byte MaterialIndexLo,
|
|
|
|
|
|
byte LightmapIndex,
|
2026-05-12 01:19:19 +03:00
|
|
|
|
ushort LocalBatchIndex,
|
2026-05-13 02:53:34 +03:00
|
|
|
|
ushort IndexCount0x06,
|
|
|
|
|
|
uint IndexStart0x06,
|
|
|
|
|
|
ushort VertexCount0x03,
|
|
|
|
|
|
uint BaseVertex0x03);
|
2026-05-09 20:36:43 +03:00
|
|
|
|
}
|
2026-05-11 06:44:49 +03:00
|
|
|
|
|
2026-05-12 01:19:19 +03:00
|
|
|
|
[Flags]
|
2026-05-11 06:44:49 +03:00
|
|
|
|
public enum BatchFlags : ushort
|
|
|
|
|
|
{
|
2026-05-12 01:19:19 +03:00
|
|
|
|
None = 0,
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Special/immediate submit path in CShade::SubmitMeshPieceBatches.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
SpecialSubmit = 0x0001,
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Intersection path tries both facing directions / disables facing test.
|
|
|
|
|
|
/// Also likely related to two-sided handling.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
DisableFacingTest = 0x0002,
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Use material phase path instead of normal material animation frame.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
UseMaterialPhase = 0x0004,
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Special pass / conditional batch logic.
|
|
|
|
|
|
/// In GetBatchRenderData this participates in conditional suppression logic.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
SpecialPassOrConditional = 0x0008,
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Batch is suppressed when paired with SpecialPassOrConditional.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
SuppressBatch = 0x0020,
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Forces second/translucent-ish render pass. Exact render-state meaning still provisional.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
ForcePass2 = 0x0100,
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Enables point-light emulation path.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
EmulatePointLights = 0x0800,
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Batch has lightmap / secondary texcoord related data.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
HasLightmapOrTexcoord1 = 0x2000,
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Facing/culling mode bit. Exact render meaning is still provisional.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
FacingTestEarlyOut = 0x4000,
|
2026-05-11 06:44:49 +03:00
|
|
|
|
}
|