mirror of
https://github.com/sampletext32/ParkanPlayground.git
synced 2026-08-15 02:57:49 +04:00
msh viewport initial
This commit is contained in:
@@ -2,7 +2,9 @@ using System.Numerics;
|
|||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using NResUI.Abstractions;
|
using NResUI.Abstractions;
|
||||||
using NResUI.Rendering.Viewport;
|
using NResUI.Rendering.Viewport;
|
||||||
|
using NativeFileDialogSharp;
|
||||||
using NResUI.Rendering.Viewport.Meshes;
|
using NResUI.Rendering.Viewport.Meshes;
|
||||||
|
using NResUI.Rendering.Viewport.Msh;
|
||||||
using Silk.NET.OpenGL;
|
using Silk.NET.OpenGL;
|
||||||
using Silk.NET.Windowing;
|
using Silk.NET.Windowing;
|
||||||
|
|
||||||
@@ -15,6 +17,9 @@ public sealed class ViewportPanel : IImGuiPanel
|
|||||||
private readonly ViewportCamera _camera = new();
|
private readonly ViewportCamera _camera = new();
|
||||||
private readonly ViewportInputController _inputController = new();
|
private readonly ViewportInputController _inputController = new();
|
||||||
|
|
||||||
|
private string? _loadedModelPath;
|
||||||
|
private string? _loadError;
|
||||||
|
|
||||||
public ViewportPanel(GL gl, IWindow window)
|
public ViewportPanel(GL gl, IWindow window)
|
||||||
{
|
{
|
||||||
var cubeMesh = PrimitiveMeshes.CreateCube(gl);
|
var cubeMesh = PrimitiveMeshes.CreateCube(gl);
|
||||||
@@ -32,6 +37,7 @@ public sealed class ViewportPanel : IImGuiPanel
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DrawModelControls();
|
||||||
DrawSelectionStatus();
|
DrawSelectionStatus();
|
||||||
DrawViewportToolbar();
|
DrawViewportToolbar();
|
||||||
DrawDebugControls();
|
DrawDebugControls();
|
||||||
@@ -73,14 +79,73 @@ public sealed class ViewportPanel : IImGuiPanel
|
|||||||
ImGui.End();
|
ImGui.End();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void DrawModelControls()
|
||||||
|
{
|
||||||
|
if (ImGui.Button("Open MSH in viewport"))
|
||||||
|
{
|
||||||
|
var result = Dialog.FileOpen("msh");
|
||||||
|
if (result.IsOk)
|
||||||
|
LoadMsh(result.Path);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
if (ImGui.Button("Reset cube"))
|
||||||
|
{
|
||||||
|
var cubeMesh = PrimitiveMeshes.CreateCube(_renderer.Gl);
|
||||||
|
_scene.ReplacePieces(new[] { ViewportPiece.CreateUnitCube(0, "Cube", cubeMesh) });
|
||||||
|
_loadedModelPath = null;
|
||||||
|
_loadError = null;
|
||||||
|
_camera.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_loadedModelPath != null)
|
||||||
|
ImGui.TextDisabled($"Model: {Path.GetFileName(_loadedModelPath)}");
|
||||||
|
|
||||||
|
if (_loadError != null)
|
||||||
|
ImGui.TextColored(new Vector4(1.0f, 0.35f, 0.25f, 1.0f), $"MSH load failed: {_loadError}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadMsh(string path)
|
||||||
|
{
|
||||||
|
var loadResult = MshViewportLoader.LoadFromFile(_renderer.Gl, path);
|
||||||
|
if (!loadResult.IsSuccess)
|
||||||
|
{
|
||||||
|
_loadError = loadResult.Error ?? "Unknown error.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_scene.ReplacePieces(loadResult.Pieces);
|
||||||
|
_loadedModelPath = loadResult.SourcePath;
|
||||||
|
_loadError = null;
|
||||||
|
|
||||||
|
if (_scene.TryGetSceneWorldBounds(out var bounds))
|
||||||
|
_camera.FrameBounds(bounds.Min, bounds.Max);
|
||||||
|
else
|
||||||
|
_camera.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawSelectionStatus()
|
private void DrawSelectionStatus()
|
||||||
{
|
{
|
||||||
var selectedPiece = _scene.SelectedPiece;
|
var selectedPiece = _scene.SelectedPiece;
|
||||||
if (selectedPiece != null)
|
if (selectedPiece != null)
|
||||||
|
{
|
||||||
ImGui.Text($"Selected: {selectedPiece.Name}");
|
ImGui.Text($"Selected: {selectedPiece.Name}");
|
||||||
|
|
||||||
|
var debugInfo = selectedPiece.DebugInfo;
|
||||||
|
if (debugInfo != null)
|
||||||
|
{
|
||||||
|
ImGui.TextDisabled(
|
||||||
|
$"{debugInfo.SourceKind} | parent {debugInfo.SourceParentIndex} | slot {debugInfo.GeometrySlotIndex} | " +
|
||||||
|
$"flags 0x{debugInfo.Msh01Flags:X4} | batches {debugInfo.BatchCount} | tris {debugInfo.TriangleCount}");
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
ImGui.TextDisabled("Selected: none");
|
ImGui.TextDisabled("Selected: none");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawViewportToolbar()
|
private void DrawViewportToolbar()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\CpDatLib\CpDatLib.csproj" />
|
<ProjectReference Include="..\CpDatLib\CpDatLib.csproj" />
|
||||||
<ProjectReference Include="..\MissionTmaLib\MissionTmaLib.csproj" />
|
<ProjectReference Include="..\MissionTmaLib\MissionTmaLib.csproj" />
|
||||||
|
<ProjectReference Include="..\MshLib\MshLib.csproj" />
|
||||||
<ProjectReference Include="..\NResLib\NResLib.csproj" />
|
<ProjectReference Include="..\NResLib\NResLib.csproj" />
|
||||||
<ProjectReference Include="..\ResTreeLib\ResTreeLib.csproj" />
|
<ProjectReference Include="..\ResTreeLib\ResTreeLib.csproj" />
|
||||||
<ProjectReference Include="..\ScrLib\ScrLib.csproj" />
|
<ProjectReference Include="..\ScrLib\ScrLib.csproj" />
|
||||||
|
|||||||
@@ -185,6 +185,15 @@ public static unsafe class PrimitiveMeshes
|
|||||||
return CreateIndexedMesh(gl, vertices, indices, PrimitiveType.Lines);
|
return CreateIndexedMesh(gl, vertices, indices, PrimitiveType.Lines);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static GpuMesh CreateColoredIndexedMesh(
|
||||||
|
GL gl,
|
||||||
|
IReadOnlyList<float> vertices,
|
||||||
|
IReadOnlyList<uint> indices,
|
||||||
|
PrimitiveType primitiveType = PrimitiveType.Triangles)
|
||||||
|
{
|
||||||
|
return CreateIndexedMesh(gl, vertices.ToArray(), indices.ToArray(), primitiveType);
|
||||||
|
}
|
||||||
|
|
||||||
private static GpuMesh CreateIndexedMesh(
|
private static GpuMesh CreateIndexedMesh(
|
||||||
GL gl,
|
GL gl,
|
||||||
float[] vertices,
|
float[] vertices,
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
namespace NResUI.Rendering.Viewport.Msh;
|
||||||
|
|
||||||
|
public sealed class MshViewportLoadResult
|
||||||
|
{
|
||||||
|
public bool IsSuccess { get; }
|
||||||
|
public string? Error { get; }
|
||||||
|
public string? SourcePath { get; }
|
||||||
|
public IReadOnlyList<ViewportPiece> Pieces { get; }
|
||||||
|
|
||||||
|
private MshViewportLoadResult(
|
||||||
|
bool isSuccess,
|
||||||
|
string? error,
|
||||||
|
string? sourcePath,
|
||||||
|
IReadOnlyList<ViewportPiece> pieces)
|
||||||
|
{
|
||||||
|
IsSuccess = isSuccess;
|
||||||
|
Error = error;
|
||||||
|
SourcePath = sourcePath;
|
||||||
|
Pieces = pieces;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MshViewportLoadResult Success(string sourcePath, IReadOnlyList<ViewportPiece> pieces)
|
||||||
|
{
|
||||||
|
return new MshViewportLoadResult(true, null, sourcePath, pieces);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MshViewportLoadResult Failure(string error, string? sourcePath = null)
|
||||||
|
{
|
||||||
|
return new MshViewportLoadResult(false, error, sourcePath, Array.Empty<ViewportPiece>());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,231 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using MshLib;
|
||||||
|
using NResLib;
|
||||||
|
using NResUI.Rendering.Viewport.Meshes;
|
||||||
|
using Silk.NET.OpenGL;
|
||||||
|
|
||||||
|
namespace NResUI.Rendering.Viewport.Msh;
|
||||||
|
|
||||||
|
public static class MshViewportLoader
|
||||||
|
{
|
||||||
|
private const int DefaultModelState = 0;
|
||||||
|
private const int DefaultLod = 0;
|
||||||
|
|
||||||
|
public static MshViewportLoadResult LoadFromFile(GL gl, string path)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||||
|
var parseResult = NResParser.ReadFile(fs);
|
||||||
|
if (parseResult.Archive == null)
|
||||||
|
return MshViewportLoadResult.Failure(parseResult.Error ?? "Failed to parse MSH/NRes file.", path);
|
||||||
|
|
||||||
|
var archive = parseResult.Archive;
|
||||||
|
var meshType = MshConverter.DetectMeshType(archive);
|
||||||
|
if (meshType != MshType.Model)
|
||||||
|
{
|
||||||
|
return MshViewportLoadResult.Failure(
|
||||||
|
$"Only normal MSH models are supported for this viewport pass. Detected: {meshType}.",
|
||||||
|
path);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.Seek(0, SeekOrigin.Begin);
|
||||||
|
var pieces = LoadModelPieces(gl, fs, archive);
|
||||||
|
if (pieces.Count == 0)
|
||||||
|
return MshViewportLoadResult.Failure("MSH was parsed, but no renderable geometry pieces were found.", path);
|
||||||
|
|
||||||
|
return MshViewportLoadResult.Success(path, pieces);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return MshViewportLoadResult.Failure(ex.Message, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<ViewportPiece> LoadModelPieces(GL gl, FileStream fs, NResArchive archive)
|
||||||
|
{
|
||||||
|
var nodes = Msh0x01.ReadComponent(fs, archive);
|
||||||
|
var geometry = Msh0x02.ReadComponent(fs, archive);
|
||||||
|
var positions = Msh0x03.ReadComponent(fs, archive);
|
||||||
|
var indices = Msh0x06.ReadComponent(fs, archive);
|
||||||
|
var batches = Msh0x0D.ReadComponent(fs, archive);
|
||||||
|
var names = TryReadNames(fs, archive);
|
||||||
|
|
||||||
|
var pieces = new List<ViewportPiece>();
|
||||||
|
|
||||||
|
for (var nodeIndex = 0; nodeIndex < nodes.Nodes.Count; nodeIndex++)
|
||||||
|
{
|
||||||
|
var node = nodes.Nodes[nodeIndex];
|
||||||
|
var slotIndex = node.ResolveSlotIndex(DefaultModelState, DefaultLod);
|
||||||
|
if (slotIndex == ushort.MaxValue)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (slotIndex >= geometry.Slots.Count)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var slot = geometry.Slots[slotIndex];
|
||||||
|
if (!slot.HasBatches)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var meshBuildResult = BuildPieceMesh(gl, nodeIndex, slot, positions, indices, batches);
|
||||||
|
if (meshBuildResult == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var name = ResolvePieceName(names, nodeIndex);
|
||||||
|
var parentIndex = node.ParentIndexOrMinusOne;
|
||||||
|
|
||||||
|
pieces.Add(new ViewportPiece(
|
||||||
|
id: nodeIndex,
|
||||||
|
name: name,
|
||||||
|
mesh: meshBuildResult.Mesh,
|
||||||
|
localTransform: Matrix4x4.Identity,
|
||||||
|
boundsMin: meshBuildResult.BoundsMin,
|
||||||
|
boundsMax: meshBuildResult.BoundsMax,
|
||||||
|
debugInfo: new ViewportPieceDebugInfo
|
||||||
|
{
|
||||||
|
SourceKind = "MSH 0x01 piece",
|
||||||
|
SourcePieceIndex = nodeIndex,
|
||||||
|
SourceParentIndex = parentIndex,
|
||||||
|
GeometrySlotIndex = slotIndex,
|
||||||
|
Msh01Flags = (uint)node.Flags,
|
||||||
|
BatchCount = meshBuildResult.BatchCount,
|
||||||
|
TriangleCount = meshBuildResult.TriangleCount
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return pieces;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PieceMeshBuildResult? BuildPieceMesh(
|
||||||
|
GL gl,
|
||||||
|
int nodeIndex,
|
||||||
|
Msh0x02.GeometrySlot slot,
|
||||||
|
IReadOnlyList<Common.Vector3> positions,
|
||||||
|
IReadOnlyList<ushort> indices,
|
||||||
|
IReadOnlyList<Msh0x0D.Batch> batches)
|
||||||
|
{
|
||||||
|
var vertices = new List<float>();
|
||||||
|
var outIndices = new List<uint>();
|
||||||
|
var color = PickDebugColor(nodeIndex);
|
||||||
|
|
||||||
|
var batchCount = 0;
|
||||||
|
var triangleCount = 0;
|
||||||
|
|
||||||
|
var boundsMin = new Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity);
|
||||||
|
var boundsMax = new Vector3(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity);
|
||||||
|
|
||||||
|
for (var batchIndex = slot.BatchStart0x0D; batchIndex < slot.BatchEndExclusive0x0D; batchIndex++)
|
||||||
|
{
|
||||||
|
if (batchIndex < 0 || batchIndex >= batches.Count)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var batch = batches[batchIndex];
|
||||||
|
if (batch.IndexStart0x06 + batch.IndexCount0x06 > indices.Count)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
batchCount++;
|
||||||
|
|
||||||
|
for (var i = 0; i + 2 < batch.IndexCount0x06; i += 3)
|
||||||
|
{
|
||||||
|
var indexBase = (int)batch.IndexStart0x06 + i;
|
||||||
|
var vertexIndex0 = checked((int)batch.BaseVertex0x03 + indices[indexBase + 0]);
|
||||||
|
var vertexIndex1 = checked((int)batch.BaseVertex0x03 + indices[indexBase + 1]);
|
||||||
|
var vertexIndex2 = checked((int)batch.BaseVertex0x03 + indices[indexBase + 2]);
|
||||||
|
|
||||||
|
if (!IsValidTriangle(positions.Count, vertexIndex0, vertexIndex1, vertexIndex2))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
AddTriangleVertex(positions[vertexIndex0], color, vertices, outIndices, ref boundsMin, ref boundsMax);
|
||||||
|
AddTriangleVertex(positions[vertexIndex1], color, vertices, outIndices, ref boundsMin, ref boundsMax);
|
||||||
|
AddTriangleVertex(positions[vertexIndex2], color, vertices, outIndices, ref boundsMin, ref boundsMax);
|
||||||
|
triangleCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (triangleCount == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var mesh = PrimitiveMeshes.CreateColoredIndexedMesh(gl, vertices, outIndices, PrimitiveType.Triangles);
|
||||||
|
return new PieceMeshBuildResult(mesh, boundsMin, boundsMax, batchCount, triangleCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddTriangleVertex(
|
||||||
|
Common.Vector3 position,
|
||||||
|
Vector3 color,
|
||||||
|
List<float> vertices,
|
||||||
|
List<uint> indices,
|
||||||
|
ref Vector3 boundsMin,
|
||||||
|
ref Vector3 boundsMax)
|
||||||
|
{
|
||||||
|
var vertexIndex = (uint)(vertices.Count / 6);
|
||||||
|
|
||||||
|
vertices.Add(position.X);
|
||||||
|
vertices.Add(position.Y);
|
||||||
|
vertices.Add(position.Z);
|
||||||
|
vertices.Add(color.X);
|
||||||
|
vertices.Add(color.Y);
|
||||||
|
vertices.Add(color.Z);
|
||||||
|
|
||||||
|
indices.Add(vertexIndex);
|
||||||
|
|
||||||
|
var p = new Vector3(position.X, position.Y, position.Z);
|
||||||
|
boundsMin = Vector3.Min(boundsMin, p);
|
||||||
|
boundsMax = Vector3.Max(boundsMax, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsValidTriangle(int vertexCount, int vertexIndex0, int vertexIndex1, int vertexIndex2)
|
||||||
|
{
|
||||||
|
return IsValidVertexIndex(vertexCount, vertexIndex0) &&
|
||||||
|
IsValidVertexIndex(vertexCount, vertexIndex1) &&
|
||||||
|
IsValidVertexIndex(vertexCount, vertexIndex2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsValidVertexIndex(int vertexCount, int vertexIndex)
|
||||||
|
{
|
||||||
|
return vertexIndex >= 0 && vertexIndex < vertexCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<string> TryReadNames(FileStream fs, NResArchive archive)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Msh0x0A.ReadComponent(fs, archive);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return new List<string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ResolvePieceName(IReadOnlyList<string> names, int nodeIndex)
|
||||||
|
{
|
||||||
|
if (nodeIndex >= 0 && nodeIndex < names.Count && !string.IsNullOrWhiteSpace(names[nodeIndex]))
|
||||||
|
return $"{nodeIndex}: {names[nodeIndex]}";
|
||||||
|
|
||||||
|
return $"{nodeIndex}: piece_{nodeIndex:D3}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector3 PickDebugColor(int index)
|
||||||
|
{
|
||||||
|
ReadOnlySpan<Vector3> palette =
|
||||||
|
[
|
||||||
|
new Vector3(0.90f, 0.36f, 0.30f),
|
||||||
|
new Vector3(0.30f, 0.68f, 0.95f),
|
||||||
|
new Vector3(0.42f, 0.82f, 0.42f),
|
||||||
|
new Vector3(0.95f, 0.78f, 0.32f),
|
||||||
|
new Vector3(0.78f, 0.48f, 0.95f),
|
||||||
|
new Vector3(0.36f, 0.86f, 0.78f),
|
||||||
|
new Vector3(0.95f, 0.55f, 0.78f),
|
||||||
|
new Vector3(0.72f, 0.72f, 0.72f),
|
||||||
|
];
|
||||||
|
|
||||||
|
return palette[index % palette.Length];
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed record PieceMeshBuildResult(
|
||||||
|
GpuMesh Mesh,
|
||||||
|
Vector3 BoundsMin,
|
||||||
|
Vector3 BoundsMax,
|
||||||
|
int BatchCount,
|
||||||
|
int TriangleCount);
|
||||||
|
}
|
||||||
@@ -14,13 +14,16 @@ public sealed class ViewportPiece
|
|||||||
public Vector3 BoundsMin { get; }
|
public Vector3 BoundsMin { get; }
|
||||||
public Vector3 BoundsMax { get; }
|
public Vector3 BoundsMax { get; }
|
||||||
|
|
||||||
|
public ViewportPieceDebugInfo? DebugInfo { get; }
|
||||||
|
|
||||||
public ViewportPiece(
|
public ViewportPiece(
|
||||||
int id,
|
int id,
|
||||||
string name,
|
string name,
|
||||||
GpuMesh mesh,
|
GpuMesh mesh,
|
||||||
Matrix4x4 localTransform,
|
Matrix4x4 localTransform,
|
||||||
Vector3 boundsMin,
|
Vector3 boundsMin,
|
||||||
Vector3 boundsMax)
|
Vector3 boundsMax,
|
||||||
|
ViewportPieceDebugInfo? debugInfo = null)
|
||||||
{
|
{
|
||||||
Id = id;
|
Id = id;
|
||||||
Name = name;
|
Name = name;
|
||||||
@@ -28,6 +31,7 @@ public sealed class ViewportPiece
|
|||||||
LocalTransform = localTransform;
|
LocalTransform = localTransform;
|
||||||
BoundsMin = boundsMin;
|
BoundsMin = boundsMin;
|
||||||
BoundsMax = boundsMax;
|
BoundsMax = boundsMax;
|
||||||
|
DebugInfo = debugInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ViewportPiece CreateUnitCube(int id, string name, GpuMesh mesh)
|
public static ViewportPiece CreateUnitCube(int id, string name, GpuMesh mesh)
|
||||||
@@ -38,6 +42,16 @@ public sealed class ViewportPiece
|
|||||||
mesh,
|
mesh,
|
||||||
Matrix4x4.Identity,
|
Matrix4x4.Identity,
|
||||||
new Vector3(-1.0f, -1.0f, -1.0f),
|
new Vector3(-1.0f, -1.0f, -1.0f),
|
||||||
new Vector3(1.0f, 1.0f, 1.0f));
|
new Vector3(1.0f, 1.0f, 1.0f),
|
||||||
|
new ViewportPieceDebugInfo
|
||||||
|
{
|
||||||
|
SourceKind = "Debug primitive",
|
||||||
|
SourcePieceIndex = id,
|
||||||
|
SourceParentIndex = -1,
|
||||||
|
GeometrySlotIndex = 0,
|
||||||
|
Msh01Flags = 0,
|
||||||
|
BatchCount = 1,
|
||||||
|
TriangleCount = 12
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
namespace NResUI.Rendering.Viewport;
|
||||||
|
|
||||||
|
public sealed class ViewportPieceDebugInfo
|
||||||
|
{
|
||||||
|
public int SourcePieceIndex { get; init; }
|
||||||
|
public int SourceParentIndex { get; init; }
|
||||||
|
public int GeometrySlotIndex { get; init; }
|
||||||
|
public uint Msh01Flags { get; init; }
|
||||||
|
public int BatchCount { get; init; }
|
||||||
|
public int TriangleCount { get; init; }
|
||||||
|
|
||||||
|
public string SourceKind { get; init; } = "MSH";
|
||||||
|
}
|
||||||
@@ -12,6 +12,8 @@ public sealed class ViewportRenderer
|
|||||||
private readonly IWindow _window;
|
private readonly IWindow _window;
|
||||||
private readonly MsaaFramebuffer _framebuffer;
|
private readonly MsaaFramebuffer _framebuffer;
|
||||||
|
|
||||||
|
public GL Gl => _gl;
|
||||||
|
|
||||||
private ShaderProgram? _meshShader;
|
private ShaderProgram? _meshShader;
|
||||||
private ShaderProgram? _outlineShader;
|
private ShaderProgram? _outlineShader;
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,19 @@ public sealed class ViewportScene
|
|||||||
_pieces.Add(piece);
|
_pieces.Add(piece);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ClearPieces()
|
||||||
|
{
|
||||||
|
_pieces.Clear();
|
||||||
|
ClearSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ReplacePieces(IEnumerable<ViewportPiece> pieces)
|
||||||
|
{
|
||||||
|
_pieces.Clear();
|
||||||
|
_pieces.AddRange(pieces);
|
||||||
|
ClearSelection();
|
||||||
|
}
|
||||||
|
|
||||||
public void ClearSelection()
|
public void ClearSelection()
|
||||||
{
|
{
|
||||||
SelectedPieceId = -1;
|
SelectedPieceId = -1;
|
||||||
|
|||||||
Reference in New Issue
Block a user