Files
parkan-playground/ParkanPlayground/Msh0x07.cs
T

115 lines
4.8 KiB
C#
Raw Normal View History

2026-05-09 20:36:43 +03:00
using System.Buffers.Binary;
2026-05-12 01:19:19 +03:00
using Common;
2026-05-09 20:36:43 +03:00
using NResLib;
namespace ParkanPlayground;
/// <summary>
2026-05-12 01:19:19 +03:00
/// MSH-компонент 0x07: triangle descriptors.
/// Используется geometry walker-ами для raycast / point-inside / фильтрации triangle flags.
/// Геометрические vertex indices лежат не здесь, а в MSH 0x06.
2026-05-09 20:36:43 +03:00
/// </summary>
public static class Msh0x07
{
2026-05-12 01:19:19 +03:00
public const int ElementSize = 0x10;
public const float PackedNormalScale = 1.0f / 32767.0f;
public static List<TriangleDescriptor> ReadComponent(FileStream mshFs, NResArchive archive)
2026-05-09 20:36:43 +03:00
{
var entry = archive.Files.FirstOrDefault(x => x.FileType == "07 00 00 00");
if (entry is null)
{
2026-05-12 01:19:19 +03:00
throw new Exception("Archive doesn't contain triangle descriptor component (0x07)");
2026-05-09 20:36:43 +03:00
}
2026-05-12 01:19:19 +03:00
if (entry.ElementSize != ElementSize)
2026-05-09 20:36:43 +03:00
{
throw new Exception("Triangle descriptor component (0x07) element size is not 16");
}
2026-05-12 01:19:19 +03:00
if (entry.FileLength % ElementSize != 0)
2026-05-09 20:36:43 +03:00
{
2026-05-12 01:19:19 +03:00
throw new Exception("Triangle descriptor component (0x07) payload size is not divisible by 16");
2026-05-09 20:36:43 +03:00
}
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
var span = data.AsSpan();
var descriptors = new List<TriangleDescriptor>(entry.FileLength / ElementSize);
2026-05-09 20:36:43 +03:00
2026-05-12 01:19:19 +03:00
for (var offset = 0; offset < span.Length; offset += ElementSize)
{
var element = span.Slice(offset, ElementSize);
2026-05-09 20:36:43 +03:00
2026-05-12 01:19:19 +03:00
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)),
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))));
}
return descriptors;
2026-05-09 20:36:43 +03:00
}
2026-05-12 01:19:19 +03:00
/// <summary>Описатель triangle MSH 0x07, length = 0x10.</summary>
/// <param name="Flags">[0x00..0x02] Triangle flags. Используются GeometryWalkFilter require/exclude.</param>
2026-05-12 16:26:30 +03:00
/// <param name="Link0">[0x02..0x04] Указывает на треугольники в 0x07 (текущем) компоненте.</param>
/// <param name="Link1">[0x04..0x06] Указывает на треугольники в 0x07 (текущем) компоненте.</param>
/// <param name="Link2">[0x06..0x08] Указывает на треугольники в 0x07 (текущем) компоненте.</param>
2026-05-12 01:19:19 +03:00
/// <param name="PackedNormalX">[0x08..0x0A] Packed normal X, int16, scale = 1 / 32767.</param>
/// <param name="PackedNormalY">[0x0A..0x0C] Packed normal Y, int16, scale = 1 / 32767.</param>
/// <param name="PackedNormalZ">[0x0C..0x0E] Packed normal Z, int16, scale = 1 / 32767.</param>
2026-05-13 02:53:34 +03:00
/// <param name="SelectorPacked">[0x0E..0x10] Packed selectors. 3 трактуется как 0xFFFF.</param>
2026-05-09 20:36:43 +03:00
public readonly record struct TriangleDescriptor(
2026-05-12 01:19:19 +03:00
TriangleFlags Flags,
2026-05-09 20:36:43 +03:00
ushort Link0,
ushort Link1,
ushort Link2,
2026-05-12 01:19:19 +03:00
short PackedNormalX,
short PackedNormalY,
short PackedNormalZ,
2026-05-09 20:36:43 +03:00
ushort SelectorPacked)
{
2026-05-12 01:19:19 +03:00
public Vector3 Normal => new(
PackedNormalX * PackedNormalScale,
PackedNormalY * PackedNormalScale,
PackedNormalZ * PackedNormalScale);
2026-05-09 20:36:43 +03:00
public ushort GetSelector(int index)
{
if (index is < 0 or > 2)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
var selector = (SelectorPacked >> (index * 2)) & 0b11;
2026-05-12 01:19:19 +03:00
return selector == 3
? ushort.MaxValue
: (ushort)selector;
}
public bool MatchesFilter(TriangleFlags required, TriangleFlags excluded)
{
return (Flags & required) == required
&& (Flags & excluded) == 0;
2026-05-09 20:36:43 +03:00
}
}
}
2026-05-12 01:19:19 +03:00
[Flags]
public enum TriangleFlags : ushort
{
None = 0,
// Пока не называем отдельные bits, потому что мы видели только require/exclude фильтрацию.
// Добавляй конкретные имена по мере нахождения usage sites.
}