Files
parkan-playground/ParkanPlayground/Msh0x07.cs
T
2026-05-12 01:19:19 +03:00

115 lines
4.6 KiB
C#

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