This commit is contained in:
bird_egop
2026-06-06 00:42:07 +03:00
parent 49d9e23738
commit 0d8e4ba491
9 changed files with 266 additions and 35 deletions
@@ -226,7 +226,7 @@ public static unsafe class PrimitiveMeshes
BufferUsageARB.StaticDraw);
}
const uint stride = 6 * sizeof(float);
const uint stride = 9 * sizeof(float);
gl.EnableVertexAttribArray(0);
gl.VertexAttribPointer(
@@ -245,6 +245,16 @@ public static unsafe class PrimitiveMeshes
false,
stride,
(void*)(3 * sizeof(float)));
gl.EnableVertexAttribArray(2);
gl.VertexAttribPointer(
2,
3,
VertexAttribPointerType.Float,
false,
stride,
(void*)(6 * sizeof(float))
);
gl.BindVertexArray(0);
@@ -0,0 +1,156 @@
using System.Collections;
using System.Numerics;
using Common;
using MshLib;
using Quaternion = System.Numerics.Quaternion;
using Vector3 = System.Numerics.Vector3;
namespace NResUI.Rendering.Viewport.Msh;
public static class MshRestPoseBuilder
{
public static IReadOnlyList<MshPieceRestPose> BuildRestPose(Msh0x01.Msh0x01Component nodesComponent, List<Msh0x08.AnimationDescriptor> animationDescriptors)
{
var nodeList = nodesComponent.Nodes;
var animationList = animationDescriptors;
var poses = new MshPieceRestPose[nodeList.Count];
var state = new byte[nodeList.Count];
for (var nodeIndex = 0; nodeIndex < nodeList.Count; nodeIndex++)
BuildNodePose(nodeIndex, nodeList, animationList, poses, state);
return poses;
}
private static Matrix4x4 BuildNodePose(
int nodeIndex,
List<Msh0x01.Node> nodes,
List<Msh0x08.AnimationDescriptor> animationDescriptors,
MshPieceRestPose[] poses,
byte[] state)
{
if (state[nodeIndex] == 2)
return poses[nodeIndex].MeshSpaceTransform;
if (state[nodeIndex] == 1)
{
// Broken/cyclic parent chain. Keep this node local so the viewer remains usable.
return Matrix4x4.Identity;
}
state[nodeIndex] = 1;
var node = nodes[nodeIndex]!;
var parentIndex = GetParentIndex(node);
var fallbackKeyframeIndex = GetFallbackKeyframeIndex(node);
var hasFallbackPose = fallbackKeyframeIndex >= 0 && fallbackKeyframeIndex < animationDescriptors.Count;
// var localTransform = Matrix4x4.Identity;
Matrix4x4 localTransform;
if (hasFallbackPose)
{
localTransform = BuildTransformFromAnimationDescriptor(animationDescriptors[fallbackKeyframeIndex]!);
}
else
{
localTransform = Matrix4x4.Identity;
Console.WriteLine($"Node {nodeIndex} has no fallback");
}
if (parentIndex == -1)
{
localTransform.Translation = Vector3.Zero;
Console.WriteLine($"Node {nodeIndex} has no parent");
}
var meshSpaceTransform = localTransform;
if (parentIndex >= 0 && parentIndex < nodes.Count && parentIndex != nodeIndex)
{
var parentTransform = BuildNodePose(parentIndex, nodes, animationDescriptors, poses, state);
meshSpaceTransform = localTransform * parentTransform;
}
poses[nodeIndex] = new MshPieceRestPose(
NodeIndex: nodeIndex,
ParentIndex: parentIndex,
FallbackKeyframeIndex: fallbackKeyframeIndex,
HasFallbackPose: hasFallbackPose,
LocalTransform: localTransform,
MeshSpaceTransform: meshSpaceTransform);
state[nodeIndex] = 2;
return meshSpaceTransform;
}
private static int GetParentIndex(Msh0x01.Node node)
{
try
{
return Convert.ToInt32(node.ParentIndexOrMinusOne);
}
catch
{
var rawParent = Convert.ToUInt16(node.ParentIndexOrLink);
return rawParent == ushort.MaxValue ? -1 : rawParent;
}
}
private static int GetFallbackKeyframeIndex(Msh0x01.Node node)
{
try
{
var value = Convert.ToUInt16(node.FallbackKey0x08);
return value == ushort.MaxValue ? -1 : value;
}
catch
{
return -1;
}
}
private static Matrix4x4 BuildTransformFromAnimationDescriptor(Msh0x08.AnimationDescriptor descriptor)
{
var position = ToSystemVector3(descriptor.Position);
var rotation = ToSystemQuaternion(descriptor.Rotation);
var matrix = Matrix4x4.CreateFromQuaternion(rotation);
matrix.Translation = position;
return matrix;
}
private static Vector3 ToSystemVector3(dynamic vector)
{
return new Vector3(
Convert.ToSingle(vector.X),
Convert.ToSingle(vector.Y),
Convert.ToSingle(vector.Z));
}
private static Quaternion ToSystemQuaternion(UShortQuaternion packedQuaternion)
{
// MSH 0x08 stores quaternion as W, X, Y, Z.
// System.Numerics.Quaternion constructor expects X, Y, Z, W.
var q = new Quaternion(
packedQuaternion.X / 32767f,
packedQuaternion.Y / 32767f,
packedQuaternion.Z / 32767f,
packedQuaternion.W / 32767f
);
if (q.LengthSquared() < 1e-8f)
return Quaternion.Identity;
return Quaternion.Normalize(q);
}
}
public readonly record struct MshPieceRestPose(
int NodeIndex,
int ParentIndex,
int FallbackKeyframeIndex,
bool HasFallbackPose,
Matrix4x4 LocalTransform,
Matrix4x4 MeshSpaceTransform);
@@ -49,6 +49,9 @@ public static class MshViewportLoader
var positions = Msh0x03.ReadComponent(fs, archive);
var indices = Msh0x06.ReadComponent(fs, archive);
var batches = Msh0x0D.ReadComponent(fs, archive);
var animationDescriptors = Msh0x08.ReadComponent(fs, archive);
var restPoses = MshRestPoseBuilder.BuildRestPose(nodes, animationDescriptors);
var mshToViewportTransform = Matrix4x4.CreateRotationX(-MathF.PI * 0.5f);
var names = TryReadNames(fs, archive);
var pieces = new List<ViewportPiece>();
@@ -56,6 +59,7 @@ public static class MshViewportLoader
for (var nodeIndex = 0; nodeIndex < nodes.Nodes.Count; nodeIndex++)
{
var node = nodes.Nodes[nodeIndex];
var restPose = restPoses[nodeIndex];
var slotIndex = node.ResolveSlotIndex(DefaultModelState, DefaultLod);
if (slotIndex == ushort.MaxValue)
continue;
@@ -72,24 +76,24 @@ public static class MshViewportLoader
continue;
var name = ResolvePieceName(names, nodeIndex);
var parentIndex = node.ParentIndexOrMinusOne;
pieces.Add(new ViewportPiece(
id: nodeIndex,
name: name,
mesh: meshBuildResult.Mesh,
localTransform: Matrix4x4.Identity,
localTransform: restPose.MeshSpaceTransform * mshToViewportTransform,
boundsMin: meshBuildResult.BoundsMin,
boundsMax: meshBuildResult.BoundsMax,
debugInfo: new ViewportPieceDebugInfo
{
SourceKind = "MSH 0x01 piece",
SourcePieceIndex = nodeIndex,
SourceParentIndex = parentIndex,
SourceParentIndex = restPose.ParentIndex,
GeometrySlotIndex = slotIndex,
Msh01Flags = (uint)node.Flags,
BatchCount = meshBuildResult.BatchCount,
TriangleCount = meshBuildResult.TriangleCount
TriangleCount = meshBuildResult.TriangleCount,
FallbackKeyframeIndex = restPose.FallbackKeyframeIndex,
HasRestPose = restPose.HasFallbackPose
}));
}
@@ -135,9 +139,19 @@ public static class MshViewportLoader
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);
var p0 = ToNumericsVector3(positions[vertexIndex0]);
var p1 = ToNumericsVector3(positions[vertexIndex1]);
var p2 = ToNumericsVector3(positions[vertexIndex2]);
var normal = Vector3.Cross(p1 - p0, p2 - p0);
if (normal.LengthSquared() < 1e-8f)
normal = Vector3.UnitY;
else
normal = Vector3.Normalize(normal);
AddTriangleVertex(p0, color, normal, vertices, outIndices, ref boundsMin, ref boundsMax);
AddTriangleVertex(p1, color, normal, vertices, outIndices, ref boundsMin, ref boundsMax);
AddTriangleVertex(p2, color, normal, vertices, outIndices, ref boundsMin, ref boundsMax);
triangleCount++;
}
}
@@ -148,14 +162,21 @@ public static class MshViewportLoader
var mesh = PrimitiveMeshes.CreateColoredIndexedMesh(gl, vertices, outIndices, PrimitiveType.Triangles);
return new PieceMeshBuildResult(mesh, boundsMin, boundsMax, batchCount, triangleCount);
}
private static Vector3 ToNumericsVector3(Common.Vector3 position)
{
return new Vector3(position.X, position.Y, position.Z);
}
private static void AddTriangleVertex(
Common.Vector3 position,
Vector3 position,
Vector3 color,
Vector3 normal,
List<float> vertices,
List<uint> indices,
ref Vector3 boundsMin,
ref Vector3 boundsMax)
ref Vector3 boundsMax
)
{
var vertexIndex = (uint)(vertices.Count / 6);
@@ -165,6 +186,9 @@ public static class MshViewportLoader
vertices.Add(color.X);
vertices.Add(color.Y);
vertices.Add(color.Z);
vertices.Add(normal.X);
vertices.Add(normal.Y);
vertices.Add(normal.Z);
indices.Add(vertexIndex);
@@ -49,6 +49,11 @@ public sealed unsafe class ShaderProgram
_gl.UniformMatrix4(location, 1, false, (float*)&matrix);
}
public void SetVector3(int location, Vector3 value)
{
_gl.Uniform3(location, value.X, value.Y, value.Z);
}
public void SetVector4(int location, Vector4 value)
{
_gl.Uniform4(location, value.X, value.Y, value.Z, value.W);
@@ -9,5 +9,8 @@ public sealed class ViewportPieceDebugInfo
public int BatchCount { get; init; }
public int TriangleCount { get; init; }
public int FallbackKeyframeIndex { get; init; } = -1;
public bool HasRestPose { get; init; }
public string SourceKind { get; init; } = "MSH";
}
+44 -19
View File
@@ -20,7 +20,10 @@ public sealed class ViewportRenderer
private GpuMesh? _unitWireBoxMesh;
private GpuMesh? _axesMesh;
private int _meshMvpLocation;
private int _modelLocation;
private int _mvpLocation;
private int _lightDirectionLocation;
private int _outlineMvpLocation;
private int _outlineColorLocation;
@@ -44,8 +47,7 @@ public sealed class ViewportRenderer
_framebuffer.BindForRender();
_gl.Enable(EnableCap.DepthTest);
_gl.Enable(EnableCap.CullFace);
_gl.CullFace(TriangleFace.Back);
_gl.Disable(EnableCap.CullFace);
_gl.ClearColor(0.12f, 0.13f, 0.15f, 1.0f);
_gl.ClearStencil(0);
@@ -66,7 +68,6 @@ public sealed class ViewportRenderer
_gl.PolygonMode(TriangleFace.FrontAndBack, PolygonMode.Fill);
_framebuffer.Resolve();
_gl.Disable(EnableCap.CullFace);
_gl.Disable(EnableCap.DepthTest);
_gl.Disable(EnableCap.StencilTest);
_gl.UseProgram(0);
@@ -96,14 +97,12 @@ public sealed class ViewportRenderer
var mvp = model * view * projection;
_gl.Disable(EnableCap.StencilTest);
_gl.Disable(EnableCap.CullFace);
_gl.PolygonMode(TriangleFace.FrontAndBack, PolygonMode.Fill);
_meshShader.Use();
_meshShader.SetMatrix4(_meshMvpLocation, mvp);
_meshShader.SetMatrix4(_mvpLocation, mvp);
grid.Mesh.Draw();
_gl.Enable(EnableCap.CullFace);
}
private void DrawScene(
@@ -166,7 +165,6 @@ public sealed class ViewportRenderer
throw new InvalidOperationException("Viewport debug resources are not initialized.");
_gl.Disable(EnableCap.StencilTest);
_gl.Disable(EnableCap.CullFace);
_gl.Disable(EnableCap.DepthTest);
_gl.PolygonMode(TriangleFace.FrontAndBack, PolygonMode.Fill);
_gl.LineWidth(2.0f);
@@ -203,7 +201,6 @@ public sealed class ViewportRenderer
_gl.LineWidth(1.0f);
_gl.Enable(EnableCap.DepthTest);
_gl.Enable(EnableCap.CullFace);
}
private void DrawPiece(
@@ -248,8 +245,13 @@ public sealed class ViewportRenderer
var mvp = model * view * projection;
var lightDirection = Vector3.Normalize(new Vector3(-0.35f, -0.75f, -0.55f));
_meshShader.Use();
_meshShader.SetMatrix4(_meshMvpLocation, mvp);
_meshShader.SetMatrix4(_mvpLocation, mvp);
_meshShader.SetMatrix4(_modelLocation, model);
_meshShader.SetVector3(_lightDirectionLocation, lightDirection);
mesh.Draw();
}
@@ -276,30 +278,49 @@ public sealed class ViewportRenderer
{
const string meshVertexShaderSource = """
#version 330 core
layout (location = 0) in vec3 aPosition;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec3 aNormal;
uniform mat4 uModel;
uniform mat4 uMvp;
out vec3 vColor;
out vec3 vNormalWorld;
void main()
{
vColor = aColor;
vNormalWorld = mat3(transpose(inverse(uModel))) * aNormal;
gl_Position = uMvp * vec4(aPosition, 1.0);
}
""";
const string meshFragmentShaderSource = """
#version 330 core
in vec3 vColor;
in vec3 vNormalWorld;
uniform vec3 uLightDirectionWorld;
out vec4 FragColor;
void main()
{
FragColor = vec4(vColor, 1.0);
vec3 normal = normalize(vNormalWorld);
// Useful when backface display is enabled.
if (!gl_FrontFacing)
normal = -normal;
vec3 lightDir = normalize(-uLightDirectionWorld);
float diffuse = max(dot(normal, lightDir), 0.0);
float lighting = 0.35 + diffuse * 0.65;
FragColor = vec4(vColor * lighting, 1.0);
}
""";
@@ -329,9 +350,13 @@ public sealed class ViewportRenderer
""";
_meshShader = new ShaderProgram(_gl, meshVertexShaderSource, meshFragmentShaderSource, "Viewport mesh");
_outlineShader = new ShaderProgram(_gl, outlineVertexShaderSource, outlineFragmentShaderSource, "Viewport outline");
_outlineShader = new ShaderProgram(_gl, outlineVertexShaderSource, outlineFragmentShaderSource,
"Viewport outline");
_modelLocation = _meshShader.GetUniformLocation("uModel");
_mvpLocation = _meshShader.GetUniformLocation("uMvp");
_lightDirectionLocation = _meshShader.GetUniformLocation("uLightDirectionWorld");
_meshMvpLocation = _meshShader.GetUniformLocation("uMvp");
_outlineMvpLocation = _outlineShader.GetUniformLocation("uMvp");
_outlineColorLocation = _outlineShader.GetUniformLocation("uColor");
}