mirror of
https://github.com/sampletext32/ParkanPlayground.git
synced 2026-08-15 02:57:49 +04:00
material initial and pose fix
This commit is contained in:
@@ -15,11 +15,11 @@ namespace MaterialLib;
|
|||||||
/// <param name="Animations">Анимации материала в порядке чтения из файла.</param>
|
/// <param name="Animations">Анимации материала в порядке чтения из файла.</param>
|
||||||
public record MaterialFile(
|
public record MaterialFile(
|
||||||
string FileName,
|
string FileName,
|
||||||
int Version,
|
uint Version,
|
||||||
int Magic1,
|
int Magic1,
|
||||||
int MaterialRenderingType,
|
uint MaterialRenderingType,
|
||||||
bool SupportsBumpMapping,
|
bool SupportsBumpMapping,
|
||||||
int IsParticleEffect,
|
uint IsParticleEffect,
|
||||||
BlendMode SourceBlendMode,
|
BlendMode SourceBlendMode,
|
||||||
BlendMode DestBlendMode,
|
BlendMode DestBlendMode,
|
||||||
float GlobalAlphaMultiplier,
|
float GlobalAlphaMultiplier,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ namespace MaterialLib;
|
|||||||
|
|
||||||
public static class MaterialParser
|
public static class MaterialParser
|
||||||
{
|
{
|
||||||
public static MaterialFile ReadFromStream(Stream fs, string fileName, int elementCount, int magic1)
|
public static MaterialFile ReadFromStream(Stream fs, string fileName, uint elementCount, int magic1)
|
||||||
{
|
{
|
||||||
var materialRenderingType = elementCount >> 2 & 0xf;
|
var materialRenderingType = elementCount >> 2 & 0xf;
|
||||||
var supportsBumpMapping = (elementCount & 2) != 0;
|
var supportsBumpMapping = (elementCount & 2) != 0;
|
||||||
|
|||||||
@@ -268,7 +268,7 @@ public class NResExplorerPanel : IImGuiPanel
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var parseResult = MaterialLib.MaterialParser.ReadFromStream(ms, file.FileName, (int)file.ElementCount, file.Magic1);
|
var parseResult = MaterialLib.MaterialParser.ReadFromStream(ms, file.FileName, file.ElementCount, file.Magic1);
|
||||||
_materialViewModel.SetParseResult(parseResult, Path.Combine(_viewModel.Path!, file.FileName));
|
_materialViewModel.SetParseResult(parseResult, Path.Combine(_viewModel.Path!, file.FileName));
|
||||||
Console.WriteLine("Read Material from context menu");
|
Console.WriteLine("Read Material from context menu");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Silk.NET.OpenGL;
|
using Silk.NET.OpenGL;
|
||||||
|
using NResUI.Rendering.Viewport;
|
||||||
|
|
||||||
namespace NResUI.Rendering.Viewport.Meshes;
|
namespace NResUI.Rendering.Viewport.Meshes;
|
||||||
|
|
||||||
@@ -11,6 +12,7 @@ public sealed class GpuMesh
|
|||||||
public uint ElementBufferObject { get; }
|
public uint ElementBufferObject { get; }
|
||||||
public uint IndexCount { get; }
|
public uint IndexCount { get; }
|
||||||
public PrimitiveType PrimitiveType { get; }
|
public PrimitiveType PrimitiveType { get; }
|
||||||
|
public ViewportMaterial Material { get; }
|
||||||
|
|
||||||
public GpuMesh(
|
public GpuMesh(
|
||||||
GL gl,
|
GL gl,
|
||||||
@@ -18,7 +20,8 @@ public sealed class GpuMesh
|
|||||||
uint vertexBufferObject,
|
uint vertexBufferObject,
|
||||||
uint elementBufferObject,
|
uint elementBufferObject,
|
||||||
uint indexCount,
|
uint indexCount,
|
||||||
PrimitiveType primitiveType = PrimitiveType.Triangles)
|
PrimitiveType primitiveType = PrimitiveType.Triangles,
|
||||||
|
ViewportMaterial? material = null)
|
||||||
{
|
{
|
||||||
_gl = gl;
|
_gl = gl;
|
||||||
VertexArrayObject = vertexArrayObject;
|
VertexArrayObject = vertexArrayObject;
|
||||||
@@ -26,6 +29,7 @@ public sealed class GpuMesh
|
|||||||
ElementBufferObject = elementBufferObject;
|
ElementBufferObject = elementBufferObject;
|
||||||
IndexCount = indexCount;
|
IndexCount = indexCount;
|
||||||
PrimitiveType = primitiveType;
|
PrimitiveType = primitiveType;
|
||||||
|
Material = material ?? ViewportMaterial.Untextured;
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe void Draw()
|
public unsafe void Draw()
|
||||||
|
|||||||
@@ -1,100 +1,42 @@
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Silk.NET.OpenGL;
|
using Silk.NET.OpenGL;
|
||||||
|
using NResUI.Rendering.Viewport;
|
||||||
|
|
||||||
namespace NResUI.Rendering.Viewport.Meshes;
|
namespace NResUI.Rendering.Viewport.Meshes;
|
||||||
|
|
||||||
public static unsafe class PrimitiveMeshes
|
public static unsafe class PrimitiveMeshes
|
||||||
{
|
{
|
||||||
|
public const int FloatsPerVertex = 11;
|
||||||
|
|
||||||
public static GpuMesh CreateCube(GL gl)
|
public static GpuMesh CreateCube(GL gl)
|
||||||
{
|
{
|
||||||
var vertices = new List<float>();
|
var vertices = new List<float>();
|
||||||
var indices = new List<uint>();
|
var indices = new List<uint>();
|
||||||
|
|
||||||
AddCubeFace(
|
AddCubeFace(vertices, indices, new(-1, -1, 1), new( 1, -1, 1), new( 1, 1, 1), new(-1, 1, 1), new(0, 0, 1), new(1, 0, 0));
|
||||||
vertices, indices,
|
AddCubeFace(vertices, indices, new( 1, -1, -1), new(-1, -1, -1), new(-1, 1, -1), new( 1, 1, -1), new(0, 0, -1), new(0, 1, 0));
|
||||||
new Vector3(-1, -1, 1),
|
AddCubeFace(vertices, indices, new(-1, -1, -1), new(-1, -1, 1), new(-1, 1, 1), new(-1, 1, -1), new(-1, 0, 0), new(0, 0, 1));
|
||||||
new Vector3( 1, -1, 1),
|
AddCubeFace(vertices, indices, new( 1, -1, 1), new( 1, -1, -1), new( 1, 1, -1), new( 1, 1, 1), new(1, 0, 0), new(1, 1, 0));
|
||||||
new Vector3( 1, 1, 1),
|
AddCubeFace(vertices, indices, new(-1, 1, 1), new( 1, 1, 1), new( 1, 1, -1), new(-1, 1, -1), new(0, 1, 0), new(1, 0, 1));
|
||||||
new Vector3(-1, 1, 1),
|
AddCubeFace(vertices, indices, new(-1, -1, -1), new( 1, -1, -1), new( 1, -1, 1), new(-1, -1, 1), new(0, -1, 0), new(0, 1, 1));
|
||||||
new Vector3(0, 0, 1),
|
|
||||||
new Vector3(1, 0, 0));
|
|
||||||
|
|
||||||
AddCubeFace(
|
|
||||||
vertices, indices,
|
|
||||||
new Vector3( 1, -1, -1),
|
|
||||||
new Vector3(-1, -1, -1),
|
|
||||||
new Vector3(-1, 1, -1),
|
|
||||||
new Vector3( 1, 1, -1),
|
|
||||||
new Vector3(0, 0, -1),
|
|
||||||
new Vector3(0, 1, 0));
|
|
||||||
|
|
||||||
AddCubeFace(
|
|
||||||
vertices, indices,
|
|
||||||
new Vector3(-1, -1, -1),
|
|
||||||
new Vector3(-1, -1, 1),
|
|
||||||
new Vector3(-1, 1, 1),
|
|
||||||
new Vector3(-1, 1, -1),
|
|
||||||
new Vector3(-1, 0, 0),
|
|
||||||
new Vector3(0, 0, 1));
|
|
||||||
|
|
||||||
AddCubeFace(
|
|
||||||
vertices, indices,
|
|
||||||
new Vector3(1, -1, 1),
|
|
||||||
new Vector3(1, -1, -1),
|
|
||||||
new Vector3(1, 1, -1),
|
|
||||||
new Vector3(1, 1, 1),
|
|
||||||
new Vector3(1, 0, 0),
|
|
||||||
new Vector3(1, 1, 0));
|
|
||||||
|
|
||||||
AddCubeFace(
|
|
||||||
vertices, indices,
|
|
||||||
new Vector3(-1, 1, 1),
|
|
||||||
new Vector3( 1, 1, 1),
|
|
||||||
new Vector3( 1, 1, -1),
|
|
||||||
new Vector3(-1, 1, -1),
|
|
||||||
new Vector3(0, 1, 0),
|
|
||||||
new Vector3(1, 0, 1));
|
|
||||||
|
|
||||||
AddCubeFace(
|
|
||||||
vertices, indices,
|
|
||||||
new Vector3(-1, -1, -1),
|
|
||||||
new Vector3( 1, -1, -1),
|
|
||||||
new Vector3( 1, -1, 1),
|
|
||||||
new Vector3(-1, -1, 1),
|
|
||||||
new Vector3(0, -1, 0),
|
|
||||||
new Vector3(0, 1, 1));
|
|
||||||
|
|
||||||
return CreateColoredIndexedMesh(gl, vertices, indices, PrimitiveType.Triangles);
|
return CreateColoredIndexedMesh(gl, vertices, indices, PrimitiveType.Triangles);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GpuMesh CreateWorldGrid(
|
public static GpuMesh CreateWorldGrid(GL gl, int halfExtent = 20, int minorStep = 1, int majorStep = 5)
|
||||||
GL gl,
|
|
||||||
int halfExtent = 20,
|
|
||||||
int minorStep = 1,
|
|
||||||
int majorStep = 5)
|
|
||||||
{
|
{
|
||||||
if (halfExtent <= 0)
|
if (halfExtent <= 0) throw new ArgumentOutOfRangeException(nameof(halfExtent));
|
||||||
throw new ArgumentOutOfRangeException(nameof(halfExtent));
|
if (minorStep <= 0) throw new ArgumentOutOfRangeException(nameof(minorStep));
|
||||||
|
if (majorStep <= 0) throw new ArgumentOutOfRangeException(nameof(majorStep));
|
||||||
if (minorStep <= 0)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(minorStep));
|
|
||||||
|
|
||||||
if (majorStep <= 0)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(majorStep));
|
|
||||||
|
|
||||||
var vertices = new List<float>();
|
var vertices = new List<float>();
|
||||||
var indices = new List<uint>();
|
var indices = new List<uint>();
|
||||||
|
|
||||||
void AddLine(
|
void AddLine(float x0, float y0, float z0, float x1, float y1, float z1, float r, float g, float b)
|
||||||
float x0, float y0, float z0,
|
|
||||||
float x1, float y1, float z1,
|
|
||||||
float r, float g, float b)
|
|
||||||
{
|
{
|
||||||
var startIndex = (uint)(vertices.Count / 9);
|
var startIndex = (uint)(vertices.Count / FloatsPerVertex);
|
||||||
|
AddVertex(vertices, x0, y0, z0, r, g, b);
|
||||||
AddVertex(vertices, x0, y0, z0, r, g, b, 0, 1, 0);
|
AddVertex(vertices, x1, y1, z1, r, g, b);
|
||||||
AddVertex(vertices, x1, y1, z1, r, g, b, 0, 1, 0);
|
|
||||||
|
|
||||||
indices.Add(startIndex);
|
indices.Add(startIndex);
|
||||||
indices.Add(startIndex + 1);
|
indices.Add(startIndex + 1);
|
||||||
}
|
}
|
||||||
@@ -105,135 +47,54 @@ public static unsafe class PrimitiveMeshes
|
|||||||
{
|
{
|
||||||
var isAxis = i == 0;
|
var isAxis = i == 0;
|
||||||
var isMajor = i % majorStep == 0;
|
var isMajor = i % majorStep == 0;
|
||||||
|
|
||||||
var brightness = isMajor ? 0.38f : 0.24f;
|
var brightness = isMajor ? 0.38f : 0.24f;
|
||||||
var r = brightness;
|
var r = brightness;
|
||||||
var g = brightness;
|
var g = brightness;
|
||||||
var b = brightness;
|
var b = brightness;
|
||||||
|
|
||||||
// Lines parallel to X. The Z axis itself is blue.
|
AddLine(-halfExtent, y, i, halfExtent, y, i, isAxis ? 0.25f : r, isAxis ? 0.45f : g, isAxis ? 1.0f : b);
|
||||||
if (isAxis)
|
AddLine(i, y, -halfExtent, i, y, halfExtent, isAxis ? 1.0f : r, isAxis ? 0.25f : g, isAxis ? 0.25f : b);
|
||||||
{
|
|
||||||
AddLine(-halfExtent, y, i, halfExtent, y, i, 0.25f, 0.45f, 1.0f);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AddLine(-halfExtent, y, i, halfExtent, y, i, r, g, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lines parallel to Z. The X axis itself is red.
|
|
||||||
if (isAxis)
|
|
||||||
{
|
|
||||||
AddLine(i, y, -halfExtent, i, y, halfExtent, 1.0f, 0.25f, 0.25f);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AddLine(i, y, -halfExtent, i, y, halfExtent, r, g, b);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return CreateIndexedMesh(gl, vertices.ToArray(), indices.ToArray(), PrimitiveType.Lines);
|
return CreateIndexedMesh(gl, vertices.ToArray(), indices.ToArray(), PrimitiveType.Lines);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddCubeFace(
|
|
||||||
List<float> vertices,
|
|
||||||
List<uint> indices,
|
|
||||||
Vector3 a,
|
|
||||||
Vector3 b,
|
|
||||||
Vector3 c,
|
|
||||||
Vector3 d,
|
|
||||||
Vector3 normal,
|
|
||||||
Vector3 color)
|
|
||||||
{
|
|
||||||
var start = (uint)(vertices.Count / 9);
|
|
||||||
|
|
||||||
AddVertex(vertices, a.X, a.Y, a.Z, color.X, color.Y, color.Z, normal.X, normal.Y, normal.Z);
|
|
||||||
AddVertex(vertices, b.X, b.Y, b.Z, color.X, color.Y, color.Z, normal.X, normal.Y, normal.Z);
|
|
||||||
AddVertex(vertices, c.X, c.Y, c.Z, color.X, color.Y, color.Z, normal.X, normal.Y, normal.Z);
|
|
||||||
AddVertex(vertices, d.X, d.Y, d.Z, color.X, color.Y, color.Z, normal.X, normal.Y, normal.Z);
|
|
||||||
|
|
||||||
indices.Add(start + 0);
|
|
||||||
indices.Add(start + 1);
|
|
||||||
indices.Add(start + 2);
|
|
||||||
|
|
||||||
indices.Add(start + 2);
|
|
||||||
indices.Add(start + 3);
|
|
||||||
indices.Add(start + 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static GpuMesh CreateUnitWireBox(GL gl)
|
public static GpuMesh CreateUnitWireBox(GL gl)
|
||||||
{
|
{
|
||||||
var vertices = new List<float>();
|
var vertices = new List<float>();
|
||||||
var indices = new List<uint>();
|
|
||||||
|
|
||||||
var color = new Vector3(1.0f, 0.82f, 0.15f);
|
var color = new Vector3(1.0f, 0.82f, 0.15f);
|
||||||
|
|
||||||
Vector3[] corners =
|
Vector3[] corners =
|
||||||
{
|
{
|
||||||
new(-1, -1, -1),
|
new(-1, -1, -1), new( 1, -1, -1), new( 1, 1, -1), new(-1, 1, -1),
|
||||||
new( 1, -1, -1),
|
new(-1, -1, 1), new( 1, -1, 1), new( 1, 1, 1), new(-1, 1, 1),
|
||||||
new( 1, 1, -1),
|
|
||||||
new(-1, 1, -1),
|
|
||||||
new(-1, -1, 1),
|
|
||||||
new( 1, -1, 1),
|
|
||||||
new( 1, 1, 1),
|
|
||||||
new(-1, 1, 1),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var corner in corners)
|
foreach (var corner in corners)
|
||||||
{
|
|
||||||
AddVertex(vertices, corner.X, corner.Y, corner.Z, color.X, color.Y, color.Z);
|
AddVertex(vertices, corner.X, corner.Y, corner.Z, color.X, color.Y, color.Z);
|
||||||
}
|
|
||||||
|
|
||||||
uint[] lineIndices =
|
uint[] indices =
|
||||||
{
|
{
|
||||||
0, 1, 1, 2, 2, 3, 3, 0,
|
0, 1, 1, 2, 2, 3, 3, 0,
|
||||||
4, 5, 5, 6, 6, 7, 7, 4,
|
4, 5, 5, 6, 6, 7, 7, 4,
|
||||||
0, 4, 1, 5, 2, 6, 3, 7,
|
0, 4, 1, 5, 2, 6, 3, 7,
|
||||||
};
|
};
|
||||||
|
|
||||||
indices.AddRange(lineIndices);
|
return CreateIndexedMesh(gl, vertices.ToArray(), indices, PrimitiveType.Lines);
|
||||||
|
|
||||||
return CreateIndexedMesh(gl, vertices.ToArray(), indices.ToArray(), PrimitiveType.Lines);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void AddVertex(
|
|
||||||
List<float> vertices,
|
|
||||||
float x, float y, float z,
|
|
||||||
float r, float g, float b,
|
|
||||||
float nx = 0.0f,
|
|
||||||
float ny = 1.0f,
|
|
||||||
float nz = 0.0f)
|
|
||||||
{
|
|
||||||
vertices.Add(x);
|
|
||||||
vertices.Add(y);
|
|
||||||
vertices.Add(z);
|
|
||||||
|
|
||||||
vertices.Add(r);
|
|
||||||
vertices.Add(g);
|
|
||||||
vertices.Add(b);
|
|
||||||
|
|
||||||
vertices.Add(nx);
|
|
||||||
vertices.Add(ny);
|
|
||||||
vertices.Add(nz);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GpuMesh CreateAxes(GL gl, float length = 1.0f)
|
public static GpuMesh CreateAxes(GL gl, float length = 1.0f)
|
||||||
{
|
{
|
||||||
if (length <= 0.0f)
|
if (length <= 0.0f) throw new ArgumentOutOfRangeException(nameof(length));
|
||||||
throw new ArgumentOutOfRangeException(nameof(length));
|
|
||||||
|
|
||||||
var vertices = new List<float>();
|
var vertices = new List<float>();
|
||||||
var indices = new List<uint>();
|
var indices = new List<uint>();
|
||||||
|
|
||||||
void AddLine(Vector3 a, Vector3 b, Vector3 color)
|
void AddLine(Vector3 a, Vector3 b, Vector3 color)
|
||||||
{
|
{
|
||||||
var start = (uint)(vertices.Count / 9);
|
var start = (uint)(vertices.Count / FloatsPerVertex);
|
||||||
|
|
||||||
AddVertex(vertices, a.X, a.Y, a.Z, color.X, color.Y, color.Z);
|
AddVertex(vertices, a.X, a.Y, a.Z, color.X, color.Y, color.Z);
|
||||||
AddVertex(vertices, b.X, b.Y, b.Z, color.X, color.Y, color.Z);
|
AddVertex(vertices, b.X, b.Y, b.Z, color.X, color.Y, color.Z);
|
||||||
|
|
||||||
indices.Add(start);
|
indices.Add(start);
|
||||||
indices.Add(start + 1);
|
indices.Add(start + 1);
|
||||||
}
|
}
|
||||||
@@ -245,20 +106,31 @@ public static unsafe class PrimitiveMeshes
|
|||||||
return CreateIndexedMesh(gl, vertices.ToArray(), indices.ToArray(), PrimitiveType.Lines);
|
return CreateIndexedMesh(gl, vertices.ToArray(), indices.ToArray(), PrimitiveType.Lines);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GpuMesh CreateColoredIndexedMesh(
|
public static GpuMesh CreateColoredIndexedMesh(GL gl, IReadOnlyList<float> vertices, IReadOnlyList<uint> indices, PrimitiveType primitiveType = PrimitiveType.Triangles, ViewportMaterial? material = null)
|
||||||
GL gl,
|
|
||||||
IReadOnlyList<float> vertices,
|
|
||||||
IReadOnlyList<uint> indices,
|
|
||||||
PrimitiveType primitiveType = PrimitiveType.Triangles)
|
|
||||||
{
|
{
|
||||||
return CreateIndexedMesh(gl, vertices.ToArray(), indices.ToArray(), primitiveType);
|
return CreateIndexedMesh(gl, vertices.ToArray(), indices.ToArray(), primitiveType, material);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static GpuMesh CreateIndexedMesh(
|
private static void AddCubeFace(List<float> vertices, List<uint> indices, Vector3 a, Vector3 b, Vector3 c, Vector3 d, Vector3 normal, Vector3 color)
|
||||||
GL gl,
|
{
|
||||||
float[] vertices,
|
var start = (uint)(vertices.Count / FloatsPerVertex);
|
||||||
uint[] indices,
|
AddVertex(vertices, a.X, a.Y, a.Z, color.X, color.Y, color.Z, normal.X, normal.Y, normal.Z);
|
||||||
PrimitiveType primitiveType)
|
AddVertex(vertices, b.X, b.Y, b.Z, color.X, color.Y, color.Z, normal.X, normal.Y, normal.Z);
|
||||||
|
AddVertex(vertices, c.X, c.Y, c.Z, color.X, color.Y, color.Z, normal.X, normal.Y, normal.Z);
|
||||||
|
AddVertex(vertices, d.X, d.Y, d.Z, color.X, color.Y, color.Z, normal.X, normal.Y, normal.Z);
|
||||||
|
indices.Add(start + 0); indices.Add(start + 1); indices.Add(start + 2);
|
||||||
|
indices.Add(start + 2); indices.Add(start + 3); indices.Add(start + 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddVertex(List<float> vertices, float x, float y, float z, float r, float g, float b, float nx = 0.0f, float ny = 1.0f, float nz = 0.0f, float u = 0.0f, float v = 0.0f)
|
||||||
|
{
|
||||||
|
vertices.Add(x); vertices.Add(y); vertices.Add(z);
|
||||||
|
vertices.Add(r); vertices.Add(g); vertices.Add(b);
|
||||||
|
vertices.Add(nx); vertices.Add(ny); vertices.Add(nz);
|
||||||
|
vertices.Add(u); vertices.Add(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GpuMesh CreateIndexedMesh(GL gl, float[] vertices, uint[] indices, PrimitiveType primitiveType, ViewportMaterial? material = null)
|
||||||
{
|
{
|
||||||
var vao = gl.GenVertexArray();
|
var vao = gl.GenVertexArray();
|
||||||
var vbo = gl.GenBuffer();
|
var vbo = gl.GenBuffer();
|
||||||
@@ -269,55 +141,31 @@ public static unsafe class PrimitiveMeshes
|
|||||||
gl.BindBuffer(BufferTargetARB.ArrayBuffer, vbo);
|
gl.BindBuffer(BufferTargetARB.ArrayBuffer, vbo);
|
||||||
fixed (float* vertexPtr = vertices)
|
fixed (float* vertexPtr = vertices)
|
||||||
{
|
{
|
||||||
gl.BufferData(
|
gl.BufferData(BufferTargetARB.ArrayBuffer, (nuint)(vertices.Length * sizeof(float)), vertexPtr, BufferUsageARB.StaticDraw);
|
||||||
BufferTargetARB.ArrayBuffer,
|
|
||||||
(nuint)(vertices.Length * sizeof(float)),
|
|
||||||
vertexPtr,
|
|
||||||
BufferUsageARB.StaticDraw);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gl.BindBuffer(BufferTargetARB.ElementArrayBuffer, ebo);
|
gl.BindBuffer(BufferTargetARB.ElementArrayBuffer, ebo);
|
||||||
fixed (uint* indexPtr = indices)
|
fixed (uint* indexPtr = indices)
|
||||||
{
|
{
|
||||||
gl.BufferData(
|
gl.BufferData(BufferTargetARB.ElementArrayBuffer, (nuint)(indices.Length * sizeof(uint)), indexPtr, BufferUsageARB.StaticDraw);
|
||||||
BufferTargetARB.ElementArrayBuffer,
|
|
||||||
(nuint)(indices.Length * sizeof(uint)),
|
|
||||||
indexPtr,
|
|
||||||
BufferUsageARB.StaticDraw);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint stride = 9 * sizeof(float);
|
const uint stride = FloatsPerVertex * sizeof(float);
|
||||||
|
|
||||||
gl.EnableVertexAttribArray(0);
|
gl.EnableVertexAttribArray(0);
|
||||||
gl.VertexAttribPointer(
|
gl.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, stride, null);
|
||||||
0,
|
|
||||||
3,
|
|
||||||
VertexAttribPointerType.Float,
|
|
||||||
false,
|
|
||||||
stride,
|
|
||||||
null);
|
|
||||||
|
|
||||||
gl.EnableVertexAttribArray(1);
|
gl.EnableVertexAttribArray(1);
|
||||||
gl.VertexAttribPointer(
|
gl.VertexAttribPointer(1, 3, VertexAttribPointerType.Float, false, stride, (void*)(3 * sizeof(float)));
|
||||||
1,
|
|
||||||
3,
|
|
||||||
VertexAttribPointerType.Float,
|
|
||||||
false,
|
|
||||||
stride,
|
|
||||||
(void*)(3 * sizeof(float)));
|
|
||||||
|
|
||||||
gl.EnableVertexAttribArray(2);
|
gl.EnableVertexAttribArray(2);
|
||||||
gl.VertexAttribPointer(
|
gl.VertexAttribPointer(2, 3, VertexAttribPointerType.Float, false, stride, (void*)(6 * sizeof(float)));
|
||||||
2,
|
|
||||||
3,
|
gl.EnableVertexAttribArray(3);
|
||||||
VertexAttribPointerType.Float,
|
gl.VertexAttribPointer(3, 2, VertexAttribPointerType.Float, false, stride, (void*)(9 * sizeof(float)));
|
||||||
false,
|
|
||||||
stride,
|
|
||||||
(void*)(6 * sizeof(float))
|
|
||||||
);
|
|
||||||
|
|
||||||
gl.BindVertexArray(0);
|
gl.BindVertexArray(0);
|
||||||
|
|
||||||
return new GpuMesh(gl, vao, vbo, ebo, (uint)indices.Length, primitiveType);
|
return new GpuMesh(gl, vao, vbo, ebo, (uint)indices.Length, primitiveType, material);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,7 +143,10 @@ public static class MshRestPoseBuilder
|
|||||||
if (q.LengthSquared() < 1e-8f)
|
if (q.LengthSquared() < 1e-8f)
|
||||||
return Quaternion.Identity;
|
return Quaternion.Identity;
|
||||||
|
|
||||||
return Quaternion.Normalize(q);
|
// MSH/game convention is opposite to our System.Numerics/OpenGL transform convention.
|
||||||
|
// Conjugating converts the stored piece rotation into the viewport convention.
|
||||||
|
// The MSH keyframe rotation is stored as mesh-to-parent instead of parent-to-mesh, so the viewer needs the inverse.
|
||||||
|
return Quaternion.Conjugate(q);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ public static class MshViewportLoader
|
|||||||
}
|
}
|
||||||
|
|
||||||
fs.Seek(0, SeekOrigin.Begin);
|
fs.Seek(0, SeekOrigin.Begin);
|
||||||
var pieces = LoadModelPieces(gl, fs, archive);
|
var pieces = LoadModelPieces(gl, fs, archive, path);
|
||||||
if (pieces.Count == 0)
|
if (pieces.Count == 0)
|
||||||
return MshViewportLoadResult.Failure("MSH was parsed, but no renderable geometry pieces were found.", path);
|
return MshViewportLoadResult.Failure("MSH was parsed, but no renderable geometry pieces were found.", path);
|
||||||
|
|
||||||
@@ -42,17 +42,19 @@ public static class MshViewportLoader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<ViewportPiece> LoadModelPieces(GL gl, FileStream fs, NResArchive archive)
|
private static List<ViewportPiece> LoadModelPieces(GL gl, FileStream fs, NResArchive archive, string path)
|
||||||
{
|
{
|
||||||
var nodes = Msh0x01.ReadComponent(fs, archive);
|
var nodes = Msh0x01.ReadComponent(fs, archive);
|
||||||
var geometry = Msh0x02.ReadComponent(fs, archive);
|
var geometry = Msh0x02.ReadComponent(fs, archive);
|
||||||
var positions = Msh0x03.ReadComponent(fs, archive);
|
var positions = Msh0x03.ReadComponent(fs, archive);
|
||||||
|
var uvs = TryReadUvs(fs, archive);
|
||||||
var indices = Msh0x06.ReadComponent(fs, archive);
|
var indices = Msh0x06.ReadComponent(fs, archive);
|
||||||
var batches = Msh0x0D.ReadComponent(fs, archive);
|
var batches = Msh0x0D.ReadComponent(fs, archive);
|
||||||
var animationDescriptors = Msh0x08.ReadComponent(fs, archive);
|
var animationDescriptors = Msh0x08.ReadComponent(fs, archive);
|
||||||
var restPoses = MshRestPoseBuilder.BuildRestPose(nodes, animationDescriptors);
|
var restPoses = MshRestPoseBuilder.BuildRestPose(nodes, animationDescriptors);
|
||||||
var mshToViewportTransform = Matrix4x4.CreateRotationX(-MathF.PI * 0.5f);
|
var mshToViewportTransform = Matrix4x4.CreateRotationX(-MathF.PI * 0.5f);
|
||||||
var names = TryReadNames(fs, archive);
|
var names = TryReadNames(fs, archive);
|
||||||
|
var materialLibrary = WeaMaterialLibrary.TryLoadForMsh(gl, path);
|
||||||
|
|
||||||
var pieces = new List<ViewportPiece>();
|
var pieces = new List<ViewportPiece>();
|
||||||
|
|
||||||
@@ -71,7 +73,7 @@ public static class MshViewportLoader
|
|||||||
if (!slot.HasBatches)
|
if (!slot.HasBatches)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var meshBuildResult = BuildPieceMesh(gl, nodeIndex, slot, positions, indices, batches);
|
var meshBuildResult = BuildPieceMeshes(gl, nodeIndex, slot, positions, uvs, indices, batches, materialLibrary);
|
||||||
if (meshBuildResult == null)
|
if (meshBuildResult == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -79,7 +81,7 @@ public static class MshViewportLoader
|
|||||||
pieces.Add(new ViewportPiece(
|
pieces.Add(new ViewportPiece(
|
||||||
id: nodeIndex,
|
id: nodeIndex,
|
||||||
name: name,
|
name: name,
|
||||||
mesh: meshBuildResult.Mesh,
|
meshes: meshBuildResult.Meshes,
|
||||||
localTransform: restPose.MeshSpaceTransform * mshToViewportTransform,
|
localTransform: restPose.MeshSpaceTransform * mshToViewportTransform,
|
||||||
boundsMin: meshBuildResult.BoundsMin,
|
boundsMin: meshBuildResult.BoundsMin,
|
||||||
boundsMax: meshBuildResult.BoundsMax,
|
boundsMax: meshBuildResult.BoundsMax,
|
||||||
@@ -100,17 +102,18 @@ public static class MshViewportLoader
|
|||||||
return pieces;
|
return pieces;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PieceMeshBuildResult? BuildPieceMesh(
|
private static PieceMeshBuildResult? BuildPieceMeshes(
|
||||||
GL gl,
|
GL gl,
|
||||||
int nodeIndex,
|
int nodeIndex,
|
||||||
Msh0x02.GeometrySlot slot,
|
Msh0x02.GeometrySlot slot,
|
||||||
IReadOnlyList<Common.Vector3> positions,
|
IReadOnlyList<Common.Vector3> positions,
|
||||||
|
IReadOnlyList<Msh05Uv> uvs,
|
||||||
IReadOnlyList<ushort> indices,
|
IReadOnlyList<ushort> indices,
|
||||||
IReadOnlyList<Msh0x0D.Batch> batches)
|
IReadOnlyList<Msh0x0D.Batch> batches,
|
||||||
|
WeaMaterialLibrary materialLibrary)
|
||||||
{
|
{
|
||||||
var vertices = new List<float>();
|
var meshes = new List<GpuMesh>();
|
||||||
var outIndices = new List<uint>();
|
var debugColor = PickDebugColor(nodeIndex);
|
||||||
var color = PickDebugColor(nodeIndex);
|
|
||||||
|
|
||||||
var batchCount = 0;
|
var batchCount = 0;
|
||||||
var triangleCount = 0;
|
var triangleCount = 0;
|
||||||
@@ -127,7 +130,12 @@ public static class MshViewportLoader
|
|||||||
if (batch.IndexStart0x06 + batch.IndexCount0x06 > indices.Count)
|
if (batch.IndexStart0x06 + batch.IndexCount0x06 > indices.Count)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
batchCount++;
|
var vertices = new List<float>();
|
||||||
|
var outIndices = new List<uint>();
|
||||||
|
var batchTriangleCount = 0;
|
||||||
|
|
||||||
|
var material = materialLibrary.FindMaterial(batch.MaterialIndex) ?? ViewportMaterial.Untextured;
|
||||||
|
var vertexColor = material.HasTexture ? Vector3.One : debugColor;
|
||||||
|
|
||||||
for (var i = 0; i + 2 < batch.IndexCount0x06; i += 3)
|
for (var i = 0; i + 2 < batch.IndexCount0x06; i += 3)
|
||||||
{
|
{
|
||||||
@@ -149,18 +157,33 @@ public static class MshViewportLoader
|
|||||||
else
|
else
|
||||||
normal = Vector3.Normalize(normal);
|
normal = Vector3.Normalize(normal);
|
||||||
|
|
||||||
AddTriangleVertex(p0, color, normal, vertices, outIndices, ref boundsMin, ref boundsMax);
|
AddTriangleVertex(p0, vertexColor, normal, GetUv(uvs, vertexIndex0), vertices, outIndices, ref boundsMin, ref boundsMax);
|
||||||
AddTriangleVertex(p1, color, normal, vertices, outIndices, ref boundsMin, ref boundsMax);
|
AddTriangleVertex(p1, vertexColor, normal, GetUv(uvs, vertexIndex1), vertices, outIndices, ref boundsMin, ref boundsMax);
|
||||||
AddTriangleVertex(p2, color, normal, vertices, outIndices, ref boundsMin, ref boundsMax);
|
AddTriangleVertex(p2, vertexColor, normal, GetUv(uvs, vertexIndex2), vertices, outIndices, ref boundsMin, ref boundsMax);
|
||||||
triangleCount++;
|
batchTriangleCount++;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (triangleCount == 0)
|
if (batchTriangleCount == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
batchCount++;
|
||||||
|
triangleCount += batchTriangleCount;
|
||||||
|
meshes.Add(PrimitiveMeshes.CreateColoredIndexedMesh(gl, vertices, outIndices, PrimitiveType.Triangles, material));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (triangleCount == 0 || meshes.Count == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var mesh = PrimitiveMeshes.CreateColoredIndexedMesh(gl, vertices, outIndices, PrimitiveType.Triangles);
|
return new PieceMeshBuildResult(meshes, boundsMin, boundsMax, batchCount, triangleCount);
|
||||||
return new PieceMeshBuildResult(mesh, boundsMin, boundsMax, batchCount, triangleCount);
|
}
|
||||||
|
|
||||||
|
private static Vector2 GetUv(IReadOnlyList<Msh05Uv> uvs, int vertexIndex)
|
||||||
|
{
|
||||||
|
if (vertexIndex < 0 || vertexIndex >= uvs.Count)
|
||||||
|
return Vector2.Zero;
|
||||||
|
|
||||||
|
var uv = uvs[vertexIndex];
|
||||||
|
return new Vector2(uv.U / 1024.0f, 1.0f - uv.V / 1024.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Vector3 ToNumericsVector3(Common.Vector3 position)
|
private static Vector3 ToNumericsVector3(Common.Vector3 position)
|
||||||
@@ -172,13 +195,13 @@ public static class MshViewportLoader
|
|||||||
Vector3 position,
|
Vector3 position,
|
||||||
Vector3 color,
|
Vector3 color,
|
||||||
Vector3 normal,
|
Vector3 normal,
|
||||||
|
Vector2 uv,
|
||||||
List<float> vertices,
|
List<float> vertices,
|
||||||
List<uint> indices,
|
List<uint> indices,
|
||||||
ref Vector3 boundsMin,
|
ref Vector3 boundsMin,
|
||||||
ref Vector3 boundsMax
|
ref Vector3 boundsMax)
|
||||||
)
|
|
||||||
{
|
{
|
||||||
var vertexIndex = (uint)(vertices.Count / 9);
|
var vertexIndex = (uint)(vertices.Count / PrimitiveMeshes.FloatsPerVertex);
|
||||||
|
|
||||||
vertices.Add(position.X);
|
vertices.Add(position.X);
|
||||||
vertices.Add(position.Y);
|
vertices.Add(position.Y);
|
||||||
@@ -189,12 +212,13 @@ public static class MshViewportLoader
|
|||||||
vertices.Add(normal.X);
|
vertices.Add(normal.X);
|
||||||
vertices.Add(normal.Y);
|
vertices.Add(normal.Y);
|
||||||
vertices.Add(normal.Z);
|
vertices.Add(normal.Z);
|
||||||
|
vertices.Add(uv.X);
|
||||||
|
vertices.Add(uv.Y);
|
||||||
|
|
||||||
indices.Add(vertexIndex);
|
indices.Add(vertexIndex);
|
||||||
|
|
||||||
var p = new Vector3(position.X, position.Y, position.Z);
|
boundsMin = Vector3.Min(boundsMin, position);
|
||||||
boundsMin = Vector3.Min(boundsMin, p);
|
boundsMax = Vector3.Max(boundsMax, position);
|
||||||
boundsMax = Vector3.Max(boundsMax, p);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsValidTriangle(int vertexCount, int vertexIndex0, int vertexIndex1, int vertexIndex2)
|
private static bool IsValidTriangle(int vertexCount, int vertexIndex0, int vertexIndex1, int vertexIndex2)
|
||||||
@@ -221,6 +245,18 @@ public static class MshViewportLoader
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static List<Msh05Uv> TryReadUvs(FileStream fs, NResArchive archive)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Msh0x05.ReadComponent(fs, archive);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return new List<Msh05Uv>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static string ResolvePieceName(IReadOnlyList<string> names, int nodeIndex)
|
private static string ResolvePieceName(IReadOnlyList<string> names, int nodeIndex)
|
||||||
{
|
{
|
||||||
if (nodeIndex >= 0 && nodeIndex < names.Count && !string.IsNullOrWhiteSpace(names[nodeIndex]))
|
if (nodeIndex >= 0 && nodeIndex < names.Count && !string.IsNullOrWhiteSpace(names[nodeIndex]))
|
||||||
@@ -247,7 +283,7 @@ public static class MshViewportLoader
|
|||||||
}
|
}
|
||||||
|
|
||||||
private sealed record PieceMeshBuildResult(
|
private sealed record PieceMeshBuildResult(
|
||||||
GpuMesh Mesh,
|
IReadOnlyList<GpuMesh> Meshes,
|
||||||
Vector3 BoundsMin,
|
Vector3 BoundsMin,
|
||||||
Vector3 BoundsMax,
|
Vector3 BoundsMax,
|
||||||
int BatchCount,
|
int BatchCount,
|
||||||
|
|||||||
@@ -0,0 +1,293 @@
|
|||||||
|
using MaterialLib;
|
||||||
|
using NResLib;
|
||||||
|
using NResUI.Rendering.Viewport.OpenGL;
|
||||||
|
using Silk.NET.OpenGL;
|
||||||
|
using TexmLib;
|
||||||
|
|
||||||
|
namespace NResUI.Rendering.Viewport.Msh;
|
||||||
|
|
||||||
|
public sealed class WeaMaterialLibrary
|
||||||
|
{
|
||||||
|
private readonly Dictionary<int, ViewportMaterial> _materialsById;
|
||||||
|
|
||||||
|
private WeaMaterialLibrary(Dictionary<int, ViewportMaterial> materialsById)
|
||||||
|
{
|
||||||
|
_materialsById = materialsById;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WeaMaterialLibrary Empty { get; } = new(new Dictionary<int, ViewportMaterial>());
|
||||||
|
|
||||||
|
public ViewportMaterial? FindMaterial(int id)
|
||||||
|
{
|
||||||
|
return _materialsById.TryGetValue(id, out var material)
|
||||||
|
? material
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WeaMaterialLibrary TryLoadForMsh(GL gl, string mshPath)
|
||||||
|
{
|
||||||
|
var weaPath = FindMatchingWeaPath(mshPath);
|
||||||
|
if (!File.Exists(weaPath))
|
||||||
|
return Empty;
|
||||||
|
|
||||||
|
var materialRefs = ParseMaterialRefs(weaPath);
|
||||||
|
if (materialRefs.Count == 0)
|
||||||
|
return Empty;
|
||||||
|
|
||||||
|
// TODO: Hardcoded for now
|
||||||
|
var materialLibFs = "C:\\IronStrategy\\Material.lib";
|
||||||
|
|
||||||
|
using var materialFs = new FileStream(materialLibFs, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||||
|
var parseResult = NResParser.ReadFile(materialFs);
|
||||||
|
if (parseResult.Archive == null)
|
||||||
|
return Empty;
|
||||||
|
|
||||||
|
var result = new Dictionary<int, ViewportMaterial>();
|
||||||
|
|
||||||
|
foreach (var materialRef in materialRefs)
|
||||||
|
{
|
||||||
|
var texture = TryLoadMaterialTexture(gl, materialFs, parseResult.Archive, materialRef.Name);
|
||||||
|
result[materialRef.Id] = texture != null
|
||||||
|
? new ViewportMaterial(materialRef.Name, texture.Value)
|
||||||
|
: new ViewportMaterial(materialRef.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new WeaMaterialLibrary(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? FindMatchingWeaPath(string mshPath)
|
||||||
|
{
|
||||||
|
var directory = Path.GetDirectoryName(mshPath);
|
||||||
|
if (string.IsNullOrWhiteSpace(directory) || !Directory.Exists(directory))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var mshResourceName = GetExportedResourceNameWithoutExtension(mshPath);
|
||||||
|
var mshObjectStem = ExtractObjectStem(mshResourceName);
|
||||||
|
|
||||||
|
var candidates = Directory.EnumerateFiles(directory, "*.wea", SearchOption.TopDirectoryOnly)
|
||||||
|
.Select(path => new
|
||||||
|
{
|
||||||
|
Path = path,
|
||||||
|
ResourceName = GetExportedResourceNameWithoutExtension(path),
|
||||||
|
})
|
||||||
|
.Select(x => new
|
||||||
|
{
|
||||||
|
x.Path,
|
||||||
|
x.ResourceName,
|
||||||
|
ObjectStem = ExtractObjectStem(x.ResourceName),
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// Best case: exact resource stem match after type prefix normalization.
|
||||||
|
var exact = candidates.FirstOrDefault(x => string.Equals(x.ObjectStem, mshObjectStem,
|
||||||
|
StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
if (exact != null)
|
||||||
|
return exact.Path;
|
||||||
|
|
||||||
|
// Fallback: choose a WEA whose resource name shares the longest token prefix.
|
||||||
|
var best = candidates
|
||||||
|
.Select(x => new
|
||||||
|
{
|
||||||
|
x.Path,
|
||||||
|
Score = CommonPrefixTokenCount(
|
||||||
|
SplitResourceTokens(mshObjectStem),
|
||||||
|
SplitResourceTokens(x.ObjectStem))
|
||||||
|
})
|
||||||
|
.OrderByDescending(x => x.Score)
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
return best?.Score > 0 ? best.Path : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetExportedResourceNameWithoutExtension(string path)
|
||||||
|
{
|
||||||
|
var name = Path.GetFileNameWithoutExtension(path);
|
||||||
|
|
||||||
|
// Exported files are usually like:
|
||||||
|
// 58_MESH_o_tur_ba_06
|
||||||
|
// 81_WEAR_o_tur_ba_02
|
||||||
|
//
|
||||||
|
// Strip numeric export prefix.
|
||||||
|
var firstUnderscore = name.IndexOf('_');
|
||||||
|
if (firstUnderscore > 0 &&
|
||||||
|
name[..firstUnderscore].All(char.IsDigit))
|
||||||
|
{
|
||||||
|
name = name[(firstUnderscore + 1)..];
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ExtractObjectStem(string resourceName)
|
||||||
|
{
|
||||||
|
// Normalize known resource kind prefixes.
|
||||||
|
// MESH_o_tur_ba_06 -> o_tur_ba
|
||||||
|
// WEAR_o_tur_ba_02 -> o_tur_ba
|
||||||
|
var normalized = resourceName;
|
||||||
|
|
||||||
|
foreach (var prefix in new[] { "MESH_", "WEAR_", "TEXT_", "ANIM_" })
|
||||||
|
{
|
||||||
|
if (normalized.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
normalized = normalized[prefix.Length..];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var tokens = normalized.Split('_', StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
|
return string.Join('_', tokens);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string[] SplitResourceTokens(string value)
|
||||||
|
{
|
||||||
|
return value.Split('_', StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int CommonPrefixTokenCount(string[] a, string[] b)
|
||||||
|
{
|
||||||
|
var count = Math.Min(a.Length, b.Length);
|
||||||
|
var result = 0;
|
||||||
|
|
||||||
|
for (var i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
if (!string.Equals(a[i], b[i], StringComparison.OrdinalIgnoreCase))
|
||||||
|
break;
|
||||||
|
|
||||||
|
result++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<WeaMaterialRef> ParseMaterialRefs(string weaPath)
|
||||||
|
{
|
||||||
|
var lines = File.ReadAllLines(weaPath)
|
||||||
|
.Select(x => x.Trim())
|
||||||
|
.Where(x => x.Length != 0)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (lines.Count == 0 || !int.TryParse(lines[0], out var materialCount) || materialCount <= 0)
|
||||||
|
return [];
|
||||||
|
|
||||||
|
var result = new List<WeaMaterialRef>();
|
||||||
|
|
||||||
|
for (var i = 0; i < materialCount && i + 1 < lines.Count; i++)
|
||||||
|
{
|
||||||
|
var line = lines[i + 1];
|
||||||
|
if (line.Equals("LIGHTMAPS", StringComparison.OrdinalIgnoreCase))
|
||||||
|
break;
|
||||||
|
|
||||||
|
var parts = line.Split((char[]?)null, 2,
|
||||||
|
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||||
|
if (parts.Length < 2)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!int.TryParse(parts[0], out var id))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
result.Add(new WeaMaterialRef(id, parts[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static uint? TryLoadMaterialTexture(
|
||||||
|
GL gl, FileStream materialFs, NResArchive materialArchive, string materialName
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var entry = FindMaterialEntry(materialArchive, materialName);
|
||||||
|
if (entry == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
materialFs.Seek(entry.OffsetInFile, SeekOrigin.Begin);
|
||||||
|
var materialData = new byte[entry.FileLength];
|
||||||
|
materialFs.ReadExactly(materialData, 0, materialData.Length);
|
||||||
|
|
||||||
|
using var ms = new MemoryStream(materialData, writable: false);
|
||||||
|
|
||||||
|
var materialFile = MaterialParser.ReadFromStream(ms, materialName, entry.ElementCount, entry.Magic1);
|
||||||
|
|
||||||
|
var texture = materialFile.Stages[0].TextureName;
|
||||||
|
|
||||||
|
var textureLibFs = "C:\\IronStrategy\\Textures.lib";
|
||||||
|
|
||||||
|
using var textureFs = new FileStream(textureLibFs, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||||
|
var textureResult = NResParser.ReadFile(textureFs);
|
||||||
|
if (textureResult.Archive == null)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
var textureEntry = FindTextureEntry(textureResult.Archive, texture);
|
||||||
|
if (textureEntry is null)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Didnt find texture {texture}");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
textureFs.Seek(textureEntry.OffsetInFile, SeekOrigin.Begin);
|
||||||
|
var textureData = new byte[textureEntry.FileLength];
|
||||||
|
textureFs.ReadExactly(textureData, 0, textureData.Length);
|
||||||
|
|
||||||
|
using var textureMs = new MemoryStream(textureData, writable: false);
|
||||||
|
|
||||||
|
var texmResult = TexmParser.ReadFromStream(textureMs, entry.FileName);
|
||||||
|
if (texmResult.TexmFile == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var rgba = texmResult.TexmFile.GetRgba32BytesFromMipmap(0, out var width, out var height);
|
||||||
|
return ViewportTextureLoader.CreateRgbaTexture(gl, rgba, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ListMetadataItem? FindMaterialEntry(NResArchive materialArchive, string materialName)
|
||||||
|
{
|
||||||
|
static string Normalize(string value)
|
||||||
|
{
|
||||||
|
return value.Trim().ToLowerInvariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
var normalized = Normalize(materialName);
|
||||||
|
|
||||||
|
return materialArchive.Files.FirstOrDefault(x =>
|
||||||
|
string.Equals(x.FileName, materialName, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
Normalize(x.FileName) == normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ListMetadataItem? FindTextureEntry(NResArchive archive, string name)
|
||||||
|
{
|
||||||
|
static string Normalize(string value)
|
||||||
|
{
|
||||||
|
return value.Trim().ToLowerInvariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
var normalized = Normalize(name);
|
||||||
|
|
||||||
|
return archive.Files.FirstOrDefault(x =>
|
||||||
|
string.Equals(x.FileName, name, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
Normalize(x.FileName) == normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? FindTexturesLib(string? startDirectory)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(startDirectory))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var directory = new DirectoryInfo(startDirectory);
|
||||||
|
while (directory != null)
|
||||||
|
{
|
||||||
|
var candidate = Path.Combine(directory.FullName, "Textures.lib");
|
||||||
|
if (Directory.Exists(candidate))
|
||||||
|
return candidate;
|
||||||
|
|
||||||
|
candidate = Path.Combine(directory.FullName, "textures.lib");
|
||||||
|
if (Directory.Exists(candidate))
|
||||||
|
return candidate;
|
||||||
|
|
||||||
|
directory = directory.Parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly record struct WeaMaterialRef(int Id, string Name);
|
||||||
|
}
|
||||||
@@ -54,6 +54,11 @@ public sealed unsafe class ShaderProgram
|
|||||||
_gl.Uniform3(location, value.X, value.Y, value.Z);
|
_gl.Uniform3(location, value.X, value.Y, value.Z);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetInt(int location, int value)
|
||||||
|
{
|
||||||
|
_gl.Uniform1(location, value);
|
||||||
|
}
|
||||||
|
|
||||||
public void SetVector4(int location, Vector4 value)
|
public void SetVector4(int location, Vector4 value)
|
||||||
{
|
{
|
||||||
_gl.Uniform4(location, value.X, value.Y, value.Z, value.W);
|
_gl.Uniform4(location, value.X, value.Y, value.Z, value.W);
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using Silk.NET.OpenGL;
|
||||||
|
|
||||||
|
namespace NResUI.Rendering.Viewport.OpenGL;
|
||||||
|
|
||||||
|
public static unsafe class ViewportTextureLoader
|
||||||
|
{
|
||||||
|
public static uint CreateRgbaTexture(GL gl, byte[] rgba, int width, int height)
|
||||||
|
{
|
||||||
|
var texture = gl.GenTexture();
|
||||||
|
gl.BindTexture(TextureTarget.Texture2D, texture);
|
||||||
|
|
||||||
|
fixed (byte* data = rgba)
|
||||||
|
{
|
||||||
|
gl.TexImage2D(
|
||||||
|
TextureTarget.Texture2D,
|
||||||
|
0,
|
||||||
|
InternalFormat.Rgba8,
|
||||||
|
(uint)width,
|
||||||
|
(uint)height,
|
||||||
|
0,
|
||||||
|
PixelFormat.Rgba,
|
||||||
|
PixelType.UnsignedByte,
|
||||||
|
data);
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.LinearMipmapLinear);
|
||||||
|
gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
|
||||||
|
gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat);
|
||||||
|
gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat);
|
||||||
|
gl.GenerateMipmap(TextureTarget.Texture2D);
|
||||||
|
|
||||||
|
gl.BindTexture(TextureTarget.Texture2D, 0);
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
namespace NResUI.Rendering.Viewport;
|
||||||
|
|
||||||
|
public sealed class ViewportMaterial
|
||||||
|
{
|
||||||
|
public string Name { get; }
|
||||||
|
public uint TextureHandle { get; }
|
||||||
|
public bool HasTexture => TextureHandle != 0;
|
||||||
|
|
||||||
|
public ViewportMaterial(string name, uint textureHandle = 0)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
TextureHandle = textureHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ViewportMaterial Untextured { get; } = new("Untextured");
|
||||||
|
}
|
||||||
@@ -7,7 +7,9 @@ public sealed class ViewportPiece
|
|||||||
{
|
{
|
||||||
public int Id { get; }
|
public int Id { get; }
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
public GpuMesh Mesh { get; }
|
public IReadOnlyList<GpuMesh> Meshes { get; }
|
||||||
|
|
||||||
|
public GpuMesh Mesh => Meshes[0];
|
||||||
|
|
||||||
public Matrix4x4 LocalTransform { get; set; }
|
public Matrix4x4 LocalTransform { get; set; }
|
||||||
|
|
||||||
@@ -24,10 +26,25 @@ public sealed class ViewportPiece
|
|||||||
Vector3 boundsMin,
|
Vector3 boundsMin,
|
||||||
Vector3 boundsMax,
|
Vector3 boundsMax,
|
||||||
ViewportPieceDebugInfo? debugInfo = null)
|
ViewportPieceDebugInfo? debugInfo = null)
|
||||||
|
: this(id, name, new[] { mesh }, localTransform, boundsMin, boundsMax, debugInfo)
|
||||||
{
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public ViewportPiece(
|
||||||
|
int id,
|
||||||
|
string name,
|
||||||
|
IReadOnlyList<GpuMesh> meshes,
|
||||||
|
Matrix4x4 localTransform,
|
||||||
|
Vector3 boundsMin,
|
||||||
|
Vector3 boundsMax,
|
||||||
|
ViewportPieceDebugInfo? debugInfo = null)
|
||||||
|
{
|
||||||
|
if (meshes.Count == 0)
|
||||||
|
throw new ArgumentException("A viewport piece must contain at least one mesh.", nameof(meshes));
|
||||||
|
|
||||||
Id = id;
|
Id = id;
|
||||||
Name = name;
|
Name = name;
|
||||||
Mesh = mesh;
|
Meshes = meshes;
|
||||||
LocalTransform = localTransform;
|
LocalTransform = localTransform;
|
||||||
BoundsMin = boundsMin;
|
BoundsMin = boundsMin;
|
||||||
BoundsMax = boundsMax;
|
BoundsMax = boundsMax;
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ public sealed class ViewportRenderer
|
|||||||
private int _modelLocation;
|
private int _modelLocation;
|
||||||
private int _mvpLocation;
|
private int _mvpLocation;
|
||||||
private int _lightDirectionLocation;
|
private int _lightDirectionLocation;
|
||||||
|
private int _useTextureLocation;
|
||||||
|
private int _texture0Location;
|
||||||
|
|
||||||
private int _outlineMvpLocation;
|
private int _outlineMvpLocation;
|
||||||
private int _outlineColorLocation;
|
private int _outlineColorLocation;
|
||||||
@@ -93,11 +95,13 @@ public sealed class ViewportRenderer
|
|||||||
if (grid == null || !grid.IsVisible)
|
if (grid == null || !grid.IsVisible)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
var model = grid.LocalTransform * sceneRotation;
|
||||||
|
|
||||||
_gl.Disable(EnableCap.StencilTest);
|
_gl.Disable(EnableCap.StencilTest);
|
||||||
_gl.PolygonMode(TriangleFace.FrontAndBack, PolygonMode.Fill);
|
_gl.PolygonMode(TriangleFace.FrontAndBack, PolygonMode.Fill);
|
||||||
|
|
||||||
var model = grid.LocalTransform * sceneRotation;
|
|
||||||
DrawMesh(grid.Mesh, model, view, projection);
|
DrawMesh(grid.Mesh, model, view, projection);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawScene(
|
private void DrawScene(
|
||||||
@@ -205,7 +209,8 @@ public sealed class ViewportRenderer
|
|||||||
Matrix4x4 projection)
|
Matrix4x4 projection)
|
||||||
{
|
{
|
||||||
var model = piece.LocalTransform * sceneRotation;
|
var model = piece.LocalTransform * sceneRotation;
|
||||||
DrawMesh(piece.Mesh, model, view, projection);
|
foreach (var mesh in piece.Meshes)
|
||||||
|
DrawMesh(mesh, model, view, projection);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawPieceOutline(
|
private void DrawPieceOutline(
|
||||||
@@ -226,7 +231,8 @@ public sealed class ViewportRenderer
|
|||||||
_outlineShader.SetMatrix4(_outlineMvpLocation, mvp);
|
_outlineShader.SetMatrix4(_outlineMvpLocation, mvp);
|
||||||
_outlineShader.SetVector4(_outlineColorLocation, new Vector4(1.0f, 0.82f, 0.15f, 1.0f));
|
_outlineShader.SetVector4(_outlineColorLocation, new Vector4(1.0f, 0.82f, 0.15f, 1.0f));
|
||||||
|
|
||||||
piece.Mesh.Draw();
|
foreach (var mesh in piece.Meshes)
|
||||||
|
mesh.Draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawMesh(
|
private void DrawMesh(
|
||||||
@@ -247,6 +253,19 @@ public sealed class ViewportRenderer
|
|||||||
_meshShader.SetMatrix4(_modelLocation, model);
|
_meshShader.SetMatrix4(_modelLocation, model);
|
||||||
_meshShader.SetVector3(_lightDirectionLocation, lightDirection);
|
_meshShader.SetVector3(_lightDirectionLocation, lightDirection);
|
||||||
|
|
||||||
|
if (mesh.Material.HasTexture)
|
||||||
|
{
|
||||||
|
_gl.ActiveTexture(TextureUnit.Texture0);
|
||||||
|
_gl.BindTexture(TextureTarget.Texture2D, mesh.Material.TextureHandle);
|
||||||
|
_meshShader.SetInt(_texture0Location, 0);
|
||||||
|
_meshShader.SetInt(_useTextureLocation, 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_gl.BindTexture(TextureTarget.Texture2D, 0);
|
||||||
|
_meshShader.SetInt(_useTextureLocation, 0);
|
||||||
|
}
|
||||||
|
|
||||||
mesh.Draw();
|
mesh.Draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,17 +296,20 @@ public sealed class ViewportRenderer
|
|||||||
layout (location = 0) in vec3 aPosition;
|
layout (location = 0) in vec3 aPosition;
|
||||||
layout (location = 1) in vec3 aColor;
|
layout (location = 1) in vec3 aColor;
|
||||||
layout (location = 2) in vec3 aNormal;
|
layout (location = 2) in vec3 aNormal;
|
||||||
|
layout (location = 3) in vec2 aTexCoord;
|
||||||
|
|
||||||
uniform mat4 uModel;
|
uniform mat4 uModel;
|
||||||
uniform mat4 uMvp;
|
uniform mat4 uMvp;
|
||||||
|
|
||||||
out vec3 vColor;
|
out vec3 vColor;
|
||||||
out vec3 vNormalWorld;
|
out vec3 vNormalWorld;
|
||||||
|
out vec2 vTexCoord;
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
vColor = aColor;
|
vColor = aColor;
|
||||||
vNormalWorld = mat3(transpose(inverse(uModel))) * aNormal;
|
vNormalWorld = mat3(transpose(inverse(uModel))) * aNormal;
|
||||||
|
vTexCoord = aTexCoord;
|
||||||
gl_Position = uMvp * vec4(aPosition, 1.0);
|
gl_Position = uMvp * vec4(aPosition, 1.0);
|
||||||
}
|
}
|
||||||
""";
|
""";
|
||||||
@@ -297,8 +319,11 @@ public sealed class ViewportRenderer
|
|||||||
|
|
||||||
in vec3 vColor;
|
in vec3 vColor;
|
||||||
in vec3 vNormalWorld;
|
in vec3 vNormalWorld;
|
||||||
|
in vec2 vTexCoord;
|
||||||
|
|
||||||
uniform vec3 uLightDirectionWorld;
|
uniform vec3 uLightDirectionWorld;
|
||||||
|
uniform bool uUseTexture;
|
||||||
|
uniform sampler2D uTexture0;
|
||||||
|
|
||||||
out vec4 FragColor;
|
out vec4 FragColor;
|
||||||
|
|
||||||
@@ -315,7 +340,11 @@ public sealed class ViewportRenderer
|
|||||||
float diffuse = max(dot(normal, lightDir), 0.0);
|
float diffuse = max(dot(normal, lightDir), 0.0);
|
||||||
float lighting = 0.35 + diffuse * 0.65;
|
float lighting = 0.35 + diffuse * 0.65;
|
||||||
|
|
||||||
FragColor = vec4(vColor * lighting, 1.0);
|
vec4 texel = uUseTexture ? texture(uTexture0, vTexCoord) : vec4(1.0);
|
||||||
|
if (texel.a < 0.05)
|
||||||
|
discard;
|
||||||
|
|
||||||
|
FragColor = vec4(vColor * texel.rgb * lighting, texel.a);
|
||||||
}
|
}
|
||||||
""";
|
""";
|
||||||
|
|
||||||
@@ -351,6 +380,8 @@ public sealed class ViewportRenderer
|
|||||||
_modelLocation = _meshShader.GetUniformLocation("uModel");
|
_modelLocation = _meshShader.GetUniformLocation("uModel");
|
||||||
_mvpLocation = _meshShader.GetUniformLocation("uMvp");
|
_mvpLocation = _meshShader.GetUniformLocation("uMvp");
|
||||||
_lightDirectionLocation = _meshShader.GetUniformLocation("uLightDirectionWorld");
|
_lightDirectionLocation = _meshShader.GetUniformLocation("uLightDirectionWorld");
|
||||||
|
_useTextureLocation = _meshShader.GetUniformLocation("uUseTexture");
|
||||||
|
_texture0Location = _meshShader.GetUniformLocation("uTexture0");
|
||||||
|
|
||||||
_outlineMvpLocation = _outlineShader.GetUniformLocation("uMvp");
|
_outlineMvpLocation = _outlineShader.GetUniformLocation("uMvp");
|
||||||
_outlineColorLocation = _outlineShader.GetUniformLocation("uColor");
|
_outlineColorLocation = _outlineShader.GetUniformLocation("uColor");
|
||||||
|
|||||||
Reference in New Issue
Block a user