vibecoded inspector

This commit is contained in:
bird_egop
2026-06-09 01:46:57 +03:00
parent 70a5d0ef69
commit 325970374a
25 changed files with 1944 additions and 649 deletions
@@ -0,0 +1,93 @@
using System.Numerics;
using MshLib;
namespace NResUI.Rendering.Inspection;
/// <summary>
/// CPU-документ меша: общая модель данных для viewport и inspector до загрузки в OpenGL.
/// Здесь хранятся ссылки на исходные MSH-компоненты и уже удобные для UI metadata.
/// </summary>
public sealed class MeshDocument
{
/// <summary>Путь к MSH/NRes ресурсу, из которого построен документ.</summary>
public required string SourcePath { get; init; }
/// <summary>Тип MSH по набору компонентов. Сейчас inspector поддерживает только обычный Model.</summary>
public required MshType MeshType { get; init; }
/// <summary>Список pieces из 0x01. Каждый piece сам выбирает slot для state/LOD.</summary>
public IReadOnlyList<MeshPieceInfo> Pieces { get; init; } = [];
/// <summary>Материалы, пришедшие из WEA. Текстуры резолвятся отдельно, потому что это уже UI/OpenGL слой.</summary>
public IReadOnlyList<MeshMaterialInfo> Materials { get; init; } = [];
/// <summary>Нефатальные проблемы загрузки: отсутствующие optional-компоненты, WEA/material/texture misses.</summary>
public IReadOnlyList<string> Warnings { get; init; } = [];
/// <summary>Количество известных model states в 0x01 таблице.</summary>
public int ModelStateCount { get; init; } = Msh0x01.StateCount;
/// <summary>Количество LOD-уровней на каждый state в 0x01 таблице.</summary>
public int LodCount { get; init; } = Msh0x01.MaxLodCount;
/// <summary>Сырые MSH-потоки, нужные для перестройки GPU-мешей при смене state/LOD.</summary>
public MshModelGeometry? MshModelGeometry { get; init; }
}
/// <summary>
/// Информация об одном piece/node из 0x01.
/// Важно: state/LOD не глобальны, поэтому этот объект хранит собственную slot-таблицу.
/// </summary>
public sealed class MeshPieceInfo
{
public required int Id { get; init; }
public required string Name { get; init; }
public required int ParentId { get; init; }
public required NodeFlags Flags { get; init; }
public required IReadOnlyList<ushort> GeometrySlotsByStateAndLod { get; init; }
public required IReadOnlyList<MeshBatchInfo> Batches { get; init; }
public required Matrix4x4 LocalTransform { get; init; }
public required Matrix4x4 MeshSpaceTransform { get; init; }
public required Vector3 BoundsMin { get; init; }
public required Vector3 BoundsMax { get; init; }
public int FallbackKeyframeIndex { get; init; } = -1;
public bool HasRestPose { get; init; }
/// <summary>
/// Возвращает geometry slot 0x02 для пары state/LOD именно этого piece.
/// 0xFFFF означает, что piece в такой комбинации не должен рендериться.
/// </summary>
public ushort ResolveSlotIndex(int state, int lod)
{
var index = state * Msh0x01.MaxLodCount + lod;
return index >= 0 && index < GeometrySlotsByStateAndLod.Count
? GeometrySlotsByStateAndLod[index]
: ushort.MaxValue;
}
}
/// <summary>
/// Inspector metadata для batch 0x0D. Геометрия остается в MshModelGeometry, здесь только диапазоны и флаги.
/// </summary>
public sealed class MeshBatchInfo
{
public required int BatchIndex { get; init; }
public required int MaterialId { get; init; }
public string? MaterialName { get; init; }
public required BatchFlags Flags { get; init; }
public required uint IndexStart { get; init; }
public required int IndexCount { get; init; }
public required uint BaseVertex { get; init; }
public required int VertexCount { get; init; }
public required int TriangleCount { get; init; }
public IReadOnlyList<string> Warnings { get; init; } = [];
}
/// <summary>Материал из WEA по id. Это еще не ViewportMaterial и не OpenGL texture.</summary>
public sealed class MeshMaterialInfo
{
public required int Id { get; init; }
public required string Name { get; init; }
public string? TextureName { get; init; }
public bool HasTexture => TextureName != null;
}
@@ -0,0 +1,71 @@
namespace NResUI.Rendering.Inspection;
/// <summary>
/// Состояние просмотра меша. State/LOD задаются только для конкретных pieces,
/// потому что 0x01 хранит таблицу slot indices отдельно для каждого узла.
/// </summary>
public sealed class MeshRenderState
{
public bool UseStoredNormals { get; set; } = true;
public bool ShowEmptyPieces { get; set; } = true;
public int SelectedPieceId { get; set; } = -1;
public int SelectedBatchIndex { get; set; } = -1;
public bool IsolateSelectedPiece { get; set; }
public bool IsolateSelectedBatch { get; set; }
public HashSet<int> HiddenPieceIds { get; } = [];
public Dictionary<int, int> PieceStateOverrides { get; } = [];
public Dictionary<int, int> PieceLodOverrides { get; } = [];
public void ClearSelection()
{
SelectedPieceId = -1;
SelectedBatchIndex = -1;
IsolateSelectedPiece = false;
IsolateSelectedBatch = false;
}
public int GetPieceState(int pieceId)
{
return PieceStateOverrides.GetValueOrDefault(pieceId, 0);
}
public int GetPieceLod(int pieceId)
{
return PieceLodOverrides.GetValueOrDefault(pieceId, 0);
}
public void SetPieceStateOverride(int pieceId, int state)
{
PieceStateOverrides[pieceId] = state;
}
public void SetPieceLodOverride(int pieceId, int lod)
{
PieceLodOverrides[pieceId] = lod;
}
public void ClearPieceOverrides(int pieceId)
{
PieceStateOverrides.Remove(pieceId);
PieceLodOverrides.Remove(pieceId);
}
public void ClearAllPieceOverrides()
{
PieceStateOverrides.Clear();
PieceLodOverrides.Clear();
}
public bool IsPieceVisible(int pieceId)
{
if (HiddenPieceIds.Contains(pieceId))
return false;
return !IsolateSelectedPiece || SelectedPieceId < 0 || pieceId == SelectedPieceId;
}
public bool IsBatchVisible(int batchIndex)
{
return !IsolateSelectedBatch || SelectedBatchIndex < 0 || batchIndex == SelectedBatchIndex;
}
}
@@ -0,0 +1,30 @@
using MshLib;
namespace NResUI.Rendering.Inspection;
/// <summary>
/// Parsed MSH component data retained so state/LOD changes can rebuild GPU meshes without reparsing the file.
/// </summary>
public sealed class MshModelGeometry
{
/// <summary>Таблица узлов 0x01: именно она связывает каждый piece с geometry slot для пары state/LOD.</summary>
public required Msh0x01.Msh0x01Component Nodes { get; init; }
/// <summary>Слоты геометрии 0x02. Один piece выбирает один слот через 0x01.ResolveSlotIndex.</summary>
public required Msh0x02.Msh0x02Component GeometrySlots { get; init; }
/// <summary>Позиции вершин из 0x03.</summary>
public required IReadOnlyList<Common.Vector3> Positions { get; init; }
/// <summary>Нормали из 0x04. Компонент опционален, поэтому фабрика мешей обязана иметь fallback.</summary>
public required IReadOnlyList<Msh04Normal> Normals { get; init; }
/// <summary>UV из 0x05. Компонент опционален для части ресурсов.</summary>
public required IReadOnlyList<Msh05Uv> Uvs { get; init; }
/// <summary>Индексный буфер 0x06 для обычных моделей.</summary>
public required IReadOnlyList<ushort> Indices { get; init; }
/// <summary>Render batches 0x0D: диапазоны индексов, material id и render flags.</summary>
public required IReadOnlyList<Msh0x0D.Batch> Batches { get; init; }
}