using System.Buffers.Binary; using Common; using NResLib; namespace ParkanPlayground; /// /// MSH-компонент 0x02: общий заголовок и таблица slot. /// public static class Msh0x02 { public static Msh0x02Component ReadComponent(FileStream mshFs, NResArchive archive) { var fileEntry = archive.Files.FirstOrDefault(x => x.FileType == "02 00 00 00"); if (fileEntry is null) { throw new Exception("Archive doesn't contain slots component (0x02)"); } if (fileEntry.FileLength < 0x8c) { throw new Exception("Slots component (0x02) is smaller than the 0x8C-byte header"); } if ((fileEntry.FileLength - 0x8c) % 68 != 0) { throw new Exception("Slots component (0x02) payload after header is not divisible by 68"); } var data = new byte[fileEntry.FileLength]; mshFs.Seek(fileEntry.OffsetInFile, SeekOrigin.Begin); mshFs.ReadExactly(data, 0, data.Length); var header = data.AsSpan(0, 0x8c); // заголовок (length = 0x8C) var center = new Vector3( BinaryPrimitives.ReadSingleLittleEndian(header.Slice(0x60)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(0x64)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(0x68)) ); var centerW = BinaryPrimitives.ReadSingleLittleEndian(header.Slice(0x6c)); var bb = new BoundingBox( new Vector3( BinaryPrimitives.ReadSingleLittleEndian(header.Slice(0)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(4)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(8)) ), new Vector3( BinaryPrimitives.ReadSingleLittleEndian(header.Slice(12)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(16)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(20)) ), new Vector3( BinaryPrimitives.ReadSingleLittleEndian(header.Slice(24)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(28)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(32)) ), new Vector3( BinaryPrimitives.ReadSingleLittleEndian(header.Slice(36)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(40)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(44)) ), new Vector3( BinaryPrimitives.ReadSingleLittleEndian(header.Slice(48)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(52)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(56)) ), new Vector3( BinaryPrimitives.ReadSingleLittleEndian(header.Slice(60)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(64)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(68)) ), new Vector3( BinaryPrimitives.ReadSingleLittleEndian(header.Slice(72)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(76)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(80)) ), new Vector3( BinaryPrimitives.ReadSingleLittleEndian(header.Slice(84)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(88)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(92)) )); var bottom = new Vector3( BinaryPrimitives.ReadSingleLittleEndian(header.Slice(112)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(116)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(120)) ); var top = new Vector3( BinaryPrimitives.ReadSingleLittleEndian(header.Slice(124)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(128)), BinaryPrimitives.ReadSingleLittleEndian(header.Slice(132)) ); var xyRadius = BinaryPrimitives.ReadSingleLittleEndian(header.Slice(136)); var elements = new List(); var skippedHeader = data.AsSpan(0x8c); var slotCount = skippedHeader.Length / 0x44; for (var i = 0; i < slotCount; i++) { var baseOffset = 0x44 * i; var opaque = new uint[3]; for (var opaqueIndex = 0; opaqueIndex < opaque.Length; opaqueIndex++) { opaque[opaqueIndex] = BinaryPrimitives.ReadUInt32LittleEndian( skippedHeader.Slice(baseOffset + 0x38 + opaqueIndex * 4) ); } elements.Add(new Slot( 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 + 0x08)), BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 0x0c)), BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 0x10)) ), new Vector3( BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 0x14)), BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 0x18)), BinaryPrimitives.ReadSingleLittleEndian(skippedHeader.Slice(baseOffset + 0x1c)) ), 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, new Sphere(center.X, center.Y, center.Z, centerW), bottom, top, xyRadius), elements); } /// Результат чтения MSH-компонента 0x02. /// Заголовок 0x02 (length = 0x8C). /// Slot records после заголовка. public record class Msh0x02Component(Msh02Header Header, List Elements); /// Заголовок 0x02 (length = 0x8C). /// [0x00..0x60] Bounding box из 8 точек. /// [0x60..0x70] Bounding sphere. /// [0x70..0x7C] Нижняя точка. /// [0x7C..0x88] Верхняя точка. /// [0x88..0x8C] Радиус в плоскости XY. public record class Msh02Header( BoundingBox BoundingBox, Sphere BoundingSphere, Vector3 Bottom, Vector3 Top, float XYRadius); /// 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..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, Sphere BoundingSphere, float BaseXyArea, float BaseVolume, uint[] Opaque); /// Bounding box заголовка: 8 точек по 3 float (length = 0x60). /// [0x00..0x0C] Нижняя передняя левая точка. /// [0x0C..0x18] Нижняя передняя правая точка. /// [0x18..0x24] Нижняя задняя правая точка. /// [0x24..0x30] Нижняя задняя левая точка. /// [0x30..0x3C] Верхняя передняя левая точка. /// [0x3C..0x48] Верхняя передняя правая точка. /// [0x48..0x54] Верхняя задняя правая точка. /// [0x54..0x60] Верхняя задняя левая точка. public record BoundingBox( Vector3 BottomFrontLeft, Vector3 BottomFrontRight, Vector3 BottomBackRight, Vector3 BottomBackLeft, Vector3 TopFrontLeft, Vector3 TopFrontRight, Vector3 TopBackRight, Vector3 TopBackLeft); }