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:
@@ -268,7 +268,7 @@ public class NResExplorerPanel : IImGuiPanel
|
||||
|
||||
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));
|
||||
Console.WriteLine("Read Material from context menu");
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Silk.NET.OpenGL;
|
||||
using NResUI.Rendering.Viewport;
|
||||
|
||||
namespace NResUI.Rendering.Viewport.Meshes;
|
||||
|
||||
@@ -11,6 +12,7 @@ public sealed class GpuMesh
|
||||
public uint ElementBufferObject { get; }
|
||||
public uint IndexCount { get; }
|
||||
public PrimitiveType PrimitiveType { get; }
|
||||
public ViewportMaterial Material { get; }
|
||||
|
||||
public GpuMesh(
|
||||
GL gl,
|
||||
@@ -18,7 +20,8 @@ public sealed class GpuMesh
|
||||
uint vertexBufferObject,
|
||||
uint elementBufferObject,
|
||||
uint indexCount,
|
||||
PrimitiveType primitiveType = PrimitiveType.Triangles)
|
||||
PrimitiveType primitiveType = PrimitiveType.Triangles,
|
||||
ViewportMaterial? material = null)
|
||||
{
|
||||
_gl = gl;
|
||||
VertexArrayObject = vertexArrayObject;
|
||||
@@ -26,6 +29,7 @@ public sealed class GpuMesh
|
||||
ElementBufferObject = elementBufferObject;
|
||||
IndexCount = indexCount;
|
||||
PrimitiveType = primitiveType;
|
||||
Material = material ?? ViewportMaterial.Untextured;
|
||||
}
|
||||
|
||||
public unsafe void Draw()
|
||||
|
||||
@@ -1,100 +1,42 @@
|
||||
using System.Numerics;
|
||||
using Silk.NET.OpenGL;
|
||||
using NResUI.Rendering.Viewport;
|
||||
|
||||
namespace NResUI.Rendering.Viewport.Meshes;
|
||||
|
||||
public static unsafe class PrimitiveMeshes
|
||||
{
|
||||
public const int FloatsPerVertex = 11;
|
||||
|
||||
public static GpuMesh CreateCube(GL gl)
|
||||
{
|
||||
var vertices = new List<float>();
|
||||
var indices = new List<uint>();
|
||||
|
||||
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(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));
|
||||
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));
|
||||
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));
|
||||
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));
|
||||
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));
|
||||
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));
|
||||
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));
|
||||
|
||||
return CreateColoredIndexedMesh(gl, vertices, indices, PrimitiveType.Triangles);
|
||||
}
|
||||
|
||||
public static GpuMesh CreateWorldGrid(
|
||||
GL gl,
|
||||
int halfExtent = 20,
|
||||
int minorStep = 1,
|
||||
int majorStep = 5)
|
||||
public static GpuMesh CreateWorldGrid(GL gl, int halfExtent = 20, int minorStep = 1, int majorStep = 5)
|
||||
{
|
||||
if (halfExtent <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(halfExtent));
|
||||
|
||||
if (minorStep <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(minorStep));
|
||||
|
||||
if (majorStep <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(majorStep));
|
||||
if (halfExtent <= 0) throw new ArgumentOutOfRangeException(nameof(halfExtent));
|
||||
if (minorStep <= 0) throw new ArgumentOutOfRangeException(nameof(minorStep));
|
||||
if (majorStep <= 0) throw new ArgumentOutOfRangeException(nameof(majorStep));
|
||||
|
||||
var vertices = new List<float>();
|
||||
var indices = new List<uint>();
|
||||
|
||||
void AddLine(
|
||||
float x0, float y0, float z0,
|
||||
float x1, float y1, float z1,
|
||||
float r, float g, float b)
|
||||
void AddLine(float x0, float y0, float z0, float x1, float y1, float z1, float r, float g, float b)
|
||||
{
|
||||
var startIndex = (uint)(vertices.Count / 9);
|
||||
|
||||
AddVertex(vertices, x0, y0, z0, r, g, b, 0, 1, 0);
|
||||
AddVertex(vertices, x1, y1, z1, r, g, b, 0, 1, 0);
|
||||
|
||||
var startIndex = (uint)(vertices.Count / FloatsPerVertex);
|
||||
AddVertex(vertices, x0, y0, z0, r, g, b);
|
||||
AddVertex(vertices, x1, y1, z1, r, g, b);
|
||||
indices.Add(startIndex);
|
||||
indices.Add(startIndex + 1);
|
||||
}
|
||||
@@ -105,135 +47,54 @@ public static unsafe class PrimitiveMeshes
|
||||
{
|
||||
var isAxis = i == 0;
|
||||
var isMajor = i % majorStep == 0;
|
||||
|
||||
var brightness = isMajor ? 0.38f : 0.24f;
|
||||
var r = brightness;
|
||||
var g = brightness;
|
||||
var b = brightness;
|
||||
|
||||
// Lines parallel to X. The Z axis itself is blue.
|
||||
if (isAxis)
|
||||
{
|
||||
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);
|
||||
}
|
||||
AddLine(-halfExtent, y, i, halfExtent, y, i, isAxis ? 0.25f : r, isAxis ? 0.45f : g, isAxis ? 1.0f : b);
|
||||
AddLine(i, y, -halfExtent, i, y, halfExtent, isAxis ? 1.0f : r, isAxis ? 0.25f : g, isAxis ? 0.25f : b);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
var vertices = new List<float>();
|
||||
var indices = new List<uint>();
|
||||
|
||||
var color = new Vector3(1.0f, 0.82f, 0.15f);
|
||||
|
||||
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)
|
||||
{
|
||||
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,
|
||||
4, 5, 5, 6, 6, 7, 7, 4,
|
||||
0, 4, 1, 5, 2, 6, 3, 7,
|
||||
};
|
||||
|
||||
indices.AddRange(lineIndices);
|
||||
|
||||
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);
|
||||
return CreateIndexedMesh(gl, vertices.ToArray(), indices, PrimitiveType.Lines);
|
||||
}
|
||||
|
||||
public static GpuMesh CreateAxes(GL gl, float length = 1.0f)
|
||||
{
|
||||
if (length <= 0.0f)
|
||||
throw new ArgumentOutOfRangeException(nameof(length));
|
||||
if (length <= 0.0f) throw new ArgumentOutOfRangeException(nameof(length));
|
||||
|
||||
var vertices = new List<float>();
|
||||
var indices = new List<uint>();
|
||||
|
||||
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, b.X, b.Y, b.Z, color.X, color.Y, color.Z);
|
||||
|
||||
indices.Add(start);
|
||||
indices.Add(start + 1);
|
||||
}
|
||||
@@ -245,20 +106,31 @@ public static unsafe class PrimitiveMeshes
|
||||
return CreateIndexedMesh(gl, vertices.ToArray(), indices.ToArray(), PrimitiveType.Lines);
|
||||
}
|
||||
|
||||
public static GpuMesh CreateColoredIndexedMesh(
|
||||
GL gl,
|
||||
IReadOnlyList<float> vertices,
|
||||
IReadOnlyList<uint> indices,
|
||||
PrimitiveType primitiveType = PrimitiveType.Triangles)
|
||||
public static GpuMesh CreateColoredIndexedMesh(GL gl, IReadOnlyList<float> vertices, IReadOnlyList<uint> indices, PrimitiveType primitiveType = PrimitiveType.Triangles, ViewportMaterial? material = null)
|
||||
{
|
||||
return CreateIndexedMesh(gl, vertices.ToArray(), indices.ToArray(), primitiveType);
|
||||
return CreateIndexedMesh(gl, vertices.ToArray(), indices.ToArray(), primitiveType, material);
|
||||
}
|
||||
|
||||
private static GpuMesh CreateIndexedMesh(
|
||||
GL gl,
|
||||
float[] vertices,
|
||||
uint[] indices,
|
||||
PrimitiveType primitiveType)
|
||||
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 / FloatsPerVertex);
|
||||
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);
|
||||
}
|
||||
|
||||
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 vbo = gl.GenBuffer();
|
||||
@@ -269,55 +141,31 @@ public static unsafe class PrimitiveMeshes
|
||||
gl.BindBuffer(BufferTargetARB.ArrayBuffer, vbo);
|
||||
fixed (float* vertexPtr = vertices)
|
||||
{
|
||||
gl.BufferData(
|
||||
BufferTargetARB.ArrayBuffer,
|
||||
(nuint)(vertices.Length * sizeof(float)),
|
||||
vertexPtr,
|
||||
BufferUsageARB.StaticDraw);
|
||||
gl.BufferData(BufferTargetARB.ArrayBuffer, (nuint)(vertices.Length * sizeof(float)), vertexPtr, BufferUsageARB.StaticDraw);
|
||||
}
|
||||
|
||||
gl.BindBuffer(BufferTargetARB.ElementArrayBuffer, ebo);
|
||||
fixed (uint* indexPtr = indices)
|
||||
{
|
||||
gl.BufferData(
|
||||
BufferTargetARB.ElementArrayBuffer,
|
||||
(nuint)(indices.Length * sizeof(uint)),
|
||||
indexPtr,
|
||||
BufferUsageARB.StaticDraw);
|
||||
gl.BufferData(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.VertexAttribPointer(
|
||||
0,
|
||||
3,
|
||||
VertexAttribPointerType.Float,
|
||||
false,
|
||||
stride,
|
||||
null);
|
||||
gl.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, stride, null);
|
||||
|
||||
gl.EnableVertexAttribArray(1);
|
||||
gl.VertexAttribPointer(
|
||||
1,
|
||||
3,
|
||||
VertexAttribPointerType.Float,
|
||||
false,
|
||||
stride,
|
||||
(void*)(3 * sizeof(float)));
|
||||
|
||||
gl.VertexAttribPointer(1, 3, VertexAttribPointerType.Float, false, stride, (void*)(3 * sizeof(float)));
|
||||
|
||||
gl.EnableVertexAttribArray(2);
|
||||
gl.VertexAttribPointer(
|
||||
2,
|
||||
3,
|
||||
VertexAttribPointerType.Float,
|
||||
false,
|
||||
stride,
|
||||
(void*)(6 * sizeof(float))
|
||||
);
|
||||
gl.VertexAttribPointer(2, 3, VertexAttribPointerType.Float, false, stride, (void*)(6 * sizeof(float)));
|
||||
|
||||
gl.EnableVertexAttribArray(3);
|
||||
gl.VertexAttribPointer(3, 2, VertexAttribPointerType.Float, false, stride, (void*)(9 * sizeof(float)));
|
||||
|
||||
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)
|
||||
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);
|
||||
var pieces = LoadModelPieces(gl, fs, archive);
|
||||
var pieces = LoadModelPieces(gl, fs, archive, path);
|
||||
if (pieces.Count == 0)
|
||||
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 geometry = Msh0x02.ReadComponent(fs, archive);
|
||||
var positions = Msh0x03.ReadComponent(fs, archive);
|
||||
var uvs = TryReadUvs(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 materialLibrary = WeaMaterialLibrary.TryLoadForMsh(gl, path);
|
||||
|
||||
var pieces = new List<ViewportPiece>();
|
||||
|
||||
@@ -71,7 +73,7 @@ public static class MshViewportLoader
|
||||
if (!slot.HasBatches)
|
||||
continue;
|
||||
|
||||
var meshBuildResult = BuildPieceMesh(gl, nodeIndex, slot, positions, indices, batches);
|
||||
var meshBuildResult = BuildPieceMeshes(gl, nodeIndex, slot, positions, uvs, indices, batches, materialLibrary);
|
||||
if (meshBuildResult == null)
|
||||
continue;
|
||||
|
||||
@@ -79,7 +81,7 @@ public static class MshViewportLoader
|
||||
pieces.Add(new ViewportPiece(
|
||||
id: nodeIndex,
|
||||
name: name,
|
||||
mesh: meshBuildResult.Mesh,
|
||||
meshes: meshBuildResult.Meshes,
|
||||
localTransform: restPose.MeshSpaceTransform * mshToViewportTransform,
|
||||
boundsMin: meshBuildResult.BoundsMin,
|
||||
boundsMax: meshBuildResult.BoundsMax,
|
||||
@@ -100,17 +102,18 @@ public static class MshViewportLoader
|
||||
return pieces;
|
||||
}
|
||||
|
||||
private static PieceMeshBuildResult? BuildPieceMesh(
|
||||
private static PieceMeshBuildResult? BuildPieceMeshes(
|
||||
GL gl,
|
||||
int nodeIndex,
|
||||
Msh0x02.GeometrySlot slot,
|
||||
IReadOnlyList<Common.Vector3> positions,
|
||||
IReadOnlyList<Msh05Uv> uvs,
|
||||
IReadOnlyList<ushort> indices,
|
||||
IReadOnlyList<Msh0x0D.Batch> batches)
|
||||
IReadOnlyList<Msh0x0D.Batch> batches,
|
||||
WeaMaterialLibrary materialLibrary)
|
||||
{
|
||||
var vertices = new List<float>();
|
||||
var outIndices = new List<uint>();
|
||||
var color = PickDebugColor(nodeIndex);
|
||||
var meshes = new List<GpuMesh>();
|
||||
var debugColor = PickDebugColor(nodeIndex);
|
||||
|
||||
var batchCount = 0;
|
||||
var triangleCount = 0;
|
||||
@@ -127,7 +130,12 @@ public static class MshViewportLoader
|
||||
if (batch.IndexStart0x06 + batch.IndexCount0x06 > indices.Count)
|
||||
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)
|
||||
{
|
||||
@@ -149,20 +157,35 @@ public static class MshViewportLoader
|
||||
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++;
|
||||
AddTriangleVertex(p0, vertexColor, normal, GetUv(uvs, vertexIndex0), vertices, outIndices, ref boundsMin, ref boundsMax);
|
||||
AddTriangleVertex(p1, vertexColor, normal, GetUv(uvs, vertexIndex1), vertices, outIndices, ref boundsMin, ref boundsMax);
|
||||
AddTriangleVertex(p2, vertexColor, normal, GetUv(uvs, vertexIndex2), vertices, outIndices, ref boundsMin, ref boundsMax);
|
||||
batchTriangleCount++;
|
||||
}
|
||||
|
||||
if (batchTriangleCount == 0)
|
||||
continue;
|
||||
|
||||
batchCount++;
|
||||
triangleCount += batchTriangleCount;
|
||||
meshes.Add(PrimitiveMeshes.CreateColoredIndexedMesh(gl, vertices, outIndices, PrimitiveType.Triangles, material));
|
||||
}
|
||||
|
||||
if (triangleCount == 0)
|
||||
if (triangleCount == 0 || meshes.Count == 0)
|
||||
return null;
|
||||
|
||||
var mesh = PrimitiveMeshes.CreateColoredIndexedMesh(gl, vertices, outIndices, PrimitiveType.Triangles);
|
||||
return new PieceMeshBuildResult(mesh, boundsMin, boundsMax, batchCount, triangleCount);
|
||||
return new PieceMeshBuildResult(meshes, 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)
|
||||
{
|
||||
return new Vector3(position.X, position.Y, position.Z);
|
||||
@@ -172,13 +195,13 @@ public static class MshViewportLoader
|
||||
Vector3 position,
|
||||
Vector3 color,
|
||||
Vector3 normal,
|
||||
Vector2 uv,
|
||||
List<float> vertices,
|
||||
List<uint> indices,
|
||||
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.Y);
|
||||
@@ -189,12 +212,13 @@ public static class MshViewportLoader
|
||||
vertices.Add(normal.X);
|
||||
vertices.Add(normal.Y);
|
||||
vertices.Add(normal.Z);
|
||||
vertices.Add(uv.X);
|
||||
vertices.Add(uv.Y);
|
||||
|
||||
indices.Add(vertexIndex);
|
||||
|
||||
var p = new Vector3(position.X, position.Y, position.Z);
|
||||
boundsMin = Vector3.Min(boundsMin, p);
|
||||
boundsMax = Vector3.Max(boundsMax, p);
|
||||
boundsMin = Vector3.Min(boundsMin, position);
|
||||
boundsMax = Vector3.Max(boundsMax, position);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (nodeIndex >= 0 && nodeIndex < names.Count && !string.IsNullOrWhiteSpace(names[nodeIndex]))
|
||||
@@ -247,7 +283,7 @@ public static class MshViewportLoader
|
||||
}
|
||||
|
||||
private sealed record PieceMeshBuildResult(
|
||||
GpuMesh Mesh,
|
||||
IReadOnlyList<GpuMesh> Meshes,
|
||||
Vector3 BoundsMin,
|
||||
Vector3 BoundsMax,
|
||||
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);
|
||||
}
|
||||
|
||||
public void SetInt(int location, int value)
|
||||
{
|
||||
_gl.Uniform1(location, value);
|
||||
}
|
||||
|
||||
public void SetVector4(int location, Vector4 value)
|
||||
{
|
||||
_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 string Name { get; }
|
||||
public GpuMesh Mesh { get; }
|
||||
public IReadOnlyList<GpuMesh> Meshes { get; }
|
||||
|
||||
public GpuMesh Mesh => Meshes[0];
|
||||
|
||||
public Matrix4x4 LocalTransform { get; set; }
|
||||
|
||||
@@ -24,10 +26,25 @@ public sealed class ViewportPiece
|
||||
Vector3 boundsMin,
|
||||
Vector3 boundsMax,
|
||||
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;
|
||||
Name = name;
|
||||
Mesh = mesh;
|
||||
Meshes = meshes;
|
||||
LocalTransform = localTransform;
|
||||
BoundsMin = boundsMin;
|
||||
BoundsMax = boundsMax;
|
||||
|
||||
@@ -23,6 +23,8 @@ public sealed class ViewportRenderer
|
||||
private int _modelLocation;
|
||||
private int _mvpLocation;
|
||||
private int _lightDirectionLocation;
|
||||
private int _useTextureLocation;
|
||||
private int _texture0Location;
|
||||
|
||||
private int _outlineMvpLocation;
|
||||
private int _outlineColorLocation;
|
||||
@@ -93,11 +95,13 @@ public sealed class ViewportRenderer
|
||||
if (grid == null || !grid.IsVisible)
|
||||
return;
|
||||
|
||||
var model = grid.LocalTransform * sceneRotation;
|
||||
|
||||
_gl.Disable(EnableCap.StencilTest);
|
||||
_gl.PolygonMode(TriangleFace.FrontAndBack, PolygonMode.Fill);
|
||||
|
||||
var model = grid.LocalTransform * sceneRotation;
|
||||
DrawMesh(grid.Mesh, model, view, projection);
|
||||
|
||||
}
|
||||
|
||||
private void DrawScene(
|
||||
@@ -205,7 +209,8 @@ public sealed class ViewportRenderer
|
||||
Matrix4x4 projection)
|
||||
{
|
||||
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(
|
||||
@@ -226,7 +231,8 @@ public sealed class ViewportRenderer
|
||||
_outlineShader.SetMatrix4(_outlineMvpLocation, mvp);
|
||||
_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(
|
||||
@@ -247,6 +253,19 @@ public sealed class ViewportRenderer
|
||||
_meshShader.SetMatrix4(_modelLocation, model);
|
||||
_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();
|
||||
}
|
||||
|
||||
@@ -277,17 +296,20 @@ public sealed class ViewportRenderer
|
||||
layout (location = 0) in vec3 aPosition;
|
||||
layout (location = 1) in vec3 aColor;
|
||||
layout (location = 2) in vec3 aNormal;
|
||||
layout (location = 3) in vec2 aTexCoord;
|
||||
|
||||
uniform mat4 uModel;
|
||||
uniform mat4 uMvp;
|
||||
|
||||
out vec3 vColor;
|
||||
out vec3 vNormalWorld;
|
||||
out vec2 vTexCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
vColor = aColor;
|
||||
vNormalWorld = mat3(transpose(inverse(uModel))) * aNormal;
|
||||
vTexCoord = aTexCoord;
|
||||
gl_Position = uMvp * vec4(aPosition, 1.0);
|
||||
}
|
||||
""";
|
||||
@@ -297,8 +319,11 @@ public sealed class ViewportRenderer
|
||||
|
||||
in vec3 vColor;
|
||||
in vec3 vNormalWorld;
|
||||
in vec2 vTexCoord;
|
||||
|
||||
uniform vec3 uLightDirectionWorld;
|
||||
uniform bool uUseTexture;
|
||||
uniform sampler2D uTexture0;
|
||||
|
||||
out vec4 FragColor;
|
||||
|
||||
@@ -315,7 +340,11 @@ public sealed class ViewportRenderer
|
||||
float diffuse = max(dot(normal, lightDir), 0.0);
|
||||
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");
|
||||
_mvpLocation = _meshShader.GetUniformLocation("uMvp");
|
||||
_lightDirectionLocation = _meshShader.GetUniformLocation("uLightDirectionWorld");
|
||||
_useTextureLocation = _meshShader.GetUniformLocation("uUseTexture");
|
||||
_texture0Location = _meshShader.GetUniformLocation("uTexture0");
|
||||
|
||||
_outlineMvpLocation = _outlineShader.GetUniformLocation("uMvp");
|
||||
_outlineColorLocation = _outlineShader.GetUniformLocation("uColor");
|
||||
|
||||
Reference in New Issue
Block a user