using System.Buffers.Binary; using NResLib; namespace ParkanPlayground; /// /// MSH-компонент 0x0D: таблица render/intersection batches. /// Используется через MSH_02_geometry_slot.batch_start_0x0d / batch_count_0x0d. /// public static class Msh0x0D { public const int ElementSize = 20; public static List ReadComponent(FileStream mshFs, NResArchive archive) { 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]; mshFs.Seek(entry.OffsetInFile, SeekOrigin.Begin); mshFs.ReadExactly(data, 0, data.Length); return data .Chunk(ElementSize) .Select(x => new Batch( (BatchFlags)BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0x00)), BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0x02)), BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0x04)), BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0x06)), BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0x08)), BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(0x0A)), BinaryPrimitives.ReadUInt16LittleEndian(x.AsSpan(0x0E)), BinaryPrimitives.ReadUInt32LittleEndian(x.AsSpan(0x10)))) .ToList(); } /// MSH batch 0x0D, sizeof 0x14. /// [0x00..0x02] Флаги batch. /// [0x02..0x04] Индекс material slot. Может быть overridden CAniMesh::forced_material_index. /// [0x04..0x06] Opaque. В GetBatchRenderData попадает в packed field вместе с material/lightmap данными. /// [0x06..0x08] Локальный batch index. В GetBatchRenderData складывается с MSH_piece.local_index_base / batch base. /// [0x08..0x0A] Количество индексов из component 0x06. /// [0x0A..0x0E] Первый индекс в component 0x06. /// [0x0E..0x10] Количество вершин для render primitive. /// [0x10..0x14] Base vertex в vertex streams, включая position stream 0x03. public readonly record struct Batch( BatchFlags Flags, ushort MaterialIndex, ushort Opaque04, ushort LocalBatchIndex, ushort IndexCount0x06, uint IndexStart0x06, ushort VertexCount0x03, uint BaseVertex0x03); } [Flags] public enum BatchFlags : ushort { None = 0, /// /// Special/immediate submit path in CShade::SubmitMeshPieceBatches. /// SpecialSubmit = 0x0001, /// /// Intersection path tries both facing directions / disables facing test. /// Also likely related to two-sided handling. /// DisableFacingTest = 0x0002, /// /// Use material phase path instead of normal material animation frame. /// UseMaterialPhase = 0x0004, /// /// Special pass / conditional batch logic. /// In GetBatchRenderData this participates in conditional suppression logic. /// SpecialPassOrConditional = 0x0008, /// /// Batch is suppressed when paired with SpecialPassOrConditional. /// SuppressBatch = 0x0020, /// /// Forces second/translucent-ish render pass. Exact render-state meaning still provisional. /// ForcePass2 = 0x0100, /// /// Enables point-light emulation path. /// EmulatePointLights = 0x0800, /// /// Batch has lightmap / secondary texcoord related data. /// HasLightmapOrTexcoord1 = 0x2000, /// /// Facing/culling mode bit. Exact render meaning is still provisional. /// FacingTestEarlyOut = 0x4000, }