diff --git a/CUE4Parse b/CUE4Parse index 19dfffa9..cebbff90 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit 19dfffa9e55d0951e7da75789e65c8c4e6e62a3e +Subproject commit cebbff9035b292e81493f8b94c19d39394be2618 diff --git a/FModel/Resources/default.vert b/FModel/Resources/default.vert index 29d22213..6b365d280 100644 --- a/FModel/Resources/default.vert +++ b/FModel/Resources/default.vert @@ -6,8 +6,8 @@ layout (location = 2) in vec2 vTexCoords; layout (location = 3) in vec4 vColor; layout (location = 4) in ivec4 vBoneIds; layout (location = 5) in vec4 vWeights; +layout (location = 6) in mat4 vInstanceMatrix; -uniform mat4 uModel; uniform mat4 uView; uniform mat4 uProjection; @@ -18,10 +18,10 @@ out vec4 fColor; void main() { - gl_Position = uProjection * uView * uModel * vec4(vPos, 1.0); + gl_Position = uProjection * uView * vInstanceMatrix * vec4(vPos, 1.0); - fPos = vec3(uModel * vec4(vPos, 1.0)); - fNormal = mat3(transpose(inverse(uModel))) * vNormal; + fPos = vec3(vInstanceMatrix * vec4(vPos, 1.0)); + fNormal = mat3(transpose(inverse(vInstanceMatrix))) * vNormal; fTexCoords = vTexCoords; fColor = vColor; } diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index 15567f29..1e2dc2f9 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -23,6 +23,7 @@ using CUE4Parse.UE4.Assets.Exports.Texture; using CUE4Parse.UE4.Assets.Exports.Wwise; using CUE4Parse.UE4.IO; using CUE4Parse.UE4.Localization; +using CUE4Parse.UE4.Objects.Engine; using CUE4Parse.UE4.Oodle.Objects; using CUE4Parse.UE4.Readers; using CUE4Parse.UE4.Shaders; @@ -749,6 +750,7 @@ public class CUE4ParseViewModel : ViewModel SaveAndPlaySound(Path.Combine(TabControl.SelectedTab.Directory, TabControl.SelectedTab.Header.SubstringBeforeLast('.')).Replace('\\', '/'), audioFormat, data); return false; } + case UWorld: case UStaticMesh when UserSettings.Default.PreviewStaticMeshes: case USkeletalMesh when UserSettings.Default.PreviewSkeletalMeshes: case UMaterialInstance when UserSettings.Default.PreviewMaterials && !ModelIsOverwritingMaterial && diff --git a/FModel/Views/Snooper/BufferObject.cs b/FModel/Views/Snooper/BufferObject.cs index 79fe7058..cea445ee 100644 --- a/FModel/Views/Snooper/BufferObject.cs +++ b/FModel/Views/Snooper/BufferObject.cs @@ -9,24 +9,38 @@ public class BufferObject : IDisposable where TDataType : unmanaged private BufferTargetARB _bufferType; private GL _gl; - public unsafe BufferObject(GL gl, Span data, BufferTargetARB bufferType) + public BufferObject(GL gl, BufferTargetARB bufferType) { _gl = gl; _bufferType = bufferType; _handle = _gl.GenBuffer(); Bind(); + } + + public unsafe BufferObject(GL gl, Span data, BufferTargetARB bufferType) : this(gl, bufferType) + { fixed (void* d = data) { _gl.BufferData(bufferType, (nuint) (data.Length * sizeof(TDataType)), d, BufferUsageARB.StaticDraw); } } + public unsafe void Update(int offset, TDataType data) + { + _gl.BufferSubData(_bufferType, offset * sizeof(TDataType), (nuint) sizeof(TDataType), data); + } + public void Bind() { _gl.BindBuffer(_bufferType, _handle); } + public void Unbind() + { + _gl.BindBuffer(_bufferType, 0); + } + public void Dispose() { _gl.DeleteBuffer(_handle); diff --git a/FModel/Views/Snooper/Camera.cs b/FModel/Views/Snooper/Camera.cs index f00d84d6..201ea1a2 100644 --- a/FModel/Views/Snooper/Camera.cs +++ b/FModel/Views/Snooper/Camera.cs @@ -45,7 +45,13 @@ public class Camera //We don't want to be able to look behind us by going over our head or under our feet so make sure it stays within these bounds Pitch = Math.Clamp(Pitch, -89f, 89f); - CalculateDirection(); + var direction = Vector3.Zero; + var yaw = Helper.DegreesToRadians(Yaw); + var pitch = Helper.DegreesToRadians(Pitch); + direction.X = MathF.Cos(yaw) * MathF.Cos(pitch); + direction.Y = MathF.Sin(pitch); + direction.Z = MathF.Sin(yaw) * MathF.Cos(pitch); + Direction = Vector3.Normalize(direction); } public Matrix4x4 GetViewMatrix() @@ -57,15 +63,4 @@ public class Camera { return Matrix4x4.CreatePerspectiveFieldOfView(Helper.DegreesToRadians(Zoom), AspectRatio, Near, Far); } - - public void CalculateDirection() - { - var direction = Vector3.Zero; - var yaw = Helper.DegreesToRadians(Yaw); - var pitch = Helper.DegreesToRadians(Pitch); - direction.X = MathF.Cos(yaw) * MathF.Cos(pitch); - direction.Y = MathF.Sin(pitch); - direction.Z = MathF.Sin(yaw) * MathF.Cos(pitch); - Direction = Vector3.Normalize(direction); - } } diff --git a/FModel/Views/Snooper/Cube.cs b/FModel/Views/Snooper/Cube.cs index 96af4f37..fbe70285 100644 --- a/FModel/Views/Snooper/Cube.cs +++ b/FModel/Views/Snooper/Cube.cs @@ -62,6 +62,8 @@ public class Cube : Model }; Sections = new Section[1]; - Sections[0] = new Section(unrealMaterial.Name, 0, (uint) Indices.Length, 0, unrealMaterial); + Sections[0] = new Section(0, (uint) Indices.Length, 0, unrealMaterial); + + AddInstance(Transform.Identity); } } diff --git a/FModel/Views/Snooper/Model.cs b/FModel/Views/Snooper/Model.cs index fd7e3761..a5edb27a 100644 --- a/FModel/Views/Snooper/Model.cs +++ b/FModel/Views/Snooper/Model.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Numerics; using CUE4Parse_Conversion.Meshes.PSK; using Silk.NET.OpenGL; @@ -13,10 +14,9 @@ public class Model : IDisposable private BufferObject _ebo; private BufferObject _vbo; + private BufferObject _mvbo; private VertexArrayObject _vao; - private Shader _shader; - private uint _vertexSize = 8; // Position + Normal + UV private const uint _faceSize = 3; // just so we don't have to do .Length private readonly uint[] _facesIndex = { 1, 0, 2 }; @@ -30,7 +30,8 @@ public class Model : IDisposable public Section[] Sections; public readonly List Skeleton; - public readonly Transform Transforms = Transform.Identity; + public int TransformsCount; + public readonly List Transforms; public readonly string[] TransformsLabels = { "X Location", "Y", "Z", "X Rotation", "Y", "Z", @@ -43,9 +44,10 @@ public class Model : IDisposable { Name = name; Type = type; + Transforms = new List(); } - public Model(string name, string type, CBaseMeshLod lod, CMeshVertex[] vertices, List skeleton = null) : this(name, type) + public Model(string name, string type, CBaseMeshLod lod, CMeshVertex[] vertices, List skeleton = null, Transform transform = null) : this(name, type) { HasVertexColors = lod.VertexColors != null; if (HasVertexColors) _vertexSize += 4; // + Color @@ -54,6 +56,8 @@ public class Model : IDisposable HasBones = Skeleton != null; if (HasBones) _vertexSize += 8; // + BoneIds + BoneWeights + _vertexSize += 16; // + InstanceMatrix + var sections = lod.Sections.Value; Sections = new Section[sections.Length]; Indices = new uint[sections.Sum(section => section.NumFaces * _faceSize)]; @@ -110,6 +114,17 @@ public class Model : IDisposable } } } + + AddInstance(transform ?? Transform.Identity); + } + + public void AddInstance(Transform transform) => Transforms.Add(transform); + + public void UpdateMatrix(int index) + { + _mvbo.Bind(); + _mvbo.Update(index, Transforms[index].Matrix); + _mvbo.Unbind(); } public void Setup(GL gl) @@ -118,8 +133,6 @@ public class Model : IDisposable _handle = _gl.CreateProgram(); - _shader = new Shader(_gl); - _ebo = new BufferObject(_gl, Indices, BufferTargetARB.ElementArrayBuffer); _vbo = new BufferObject(_gl, Vertices, BufferTargetARB.ArrayBuffer); _vao = new VertexArrayObject(_gl, _vbo, _ebo); @@ -130,36 +143,34 @@ public class Model : IDisposable _vao.VertexAttributePointer(3, 4, VertexAttribPointerType.Float, _vertexSize, 8); // color _vao.VertexAttributePointer(4, 4, VertexAttribPointerType.Int, _vertexSize, 12); // boneids _vao.VertexAttributePointer(5, 4, VertexAttribPointerType.Float, _vertexSize, 16); // boneweights + _vao.VertexAttributePointer(6, 16, VertexAttribPointerType.Float, _vertexSize, 20); // instancematrix + + TransformsCount = Transforms.Count; + var instanceMatrix = new Matrix4x4[TransformsCount]; + for (var i = 0; i < instanceMatrix.Length; i++) + instanceMatrix[i] = Transforms[i].Matrix; + _mvbo = new BufferObject(_gl, instanceMatrix, BufferTargetARB.ArrayBuffer); for (int section = 0; section < Sections.Length; section++) { + _vao.Bind(); + _vao.BindInstancing(); + _vao.Unbind(); + Sections[section].Setup(_gl); } } - public void Bind(Camera camera) + public void Bind(Shader shader) { - _vao.Bind(); - - _shader.Use(); - - _shader.SetUniform("uModel", Transforms.Matrix); - _shader.SetUniform("uView", camera.GetViewMatrix()); - _shader.SetUniform("uProjection", camera.GetProjectionMatrix()); - _shader.SetUniform("viewPos", camera.Position); - - _shader.SetUniform("material.diffuseMap", 0); - _shader.SetUniform("material.normalMap", 1); - _shader.SetUniform("material.specularMap", 2); - _shader.SetUniform("material.emissionMap", 3); - - _shader.SetUniform("light.position", camera.Position); - - _shader.SetUniform("display_vertex_colors", DisplayVertexColors); + shader.SetUniform("display_vertex_colors", DisplayVertexColors); + var instanceCount = (uint) TransformsCount; for (int section = 0; section < Sections.Length; section++) { - Sections[section].Bind(_shader); + _vao.Bind(); + Sections[section].Bind(shader, instanceCount); + _vao.Unbind(); } } @@ -167,8 +178,8 @@ public class Model : IDisposable { _ebo.Dispose(); _vbo.Dispose(); + _mvbo.Dispose(); _vao.Dispose(); - _shader.Dispose(); for (int section = 0; section < Sections.Length; section++) { Sections[section].Dispose(); diff --git a/FModel/Views/Snooper/Section.cs b/FModel/Views/Snooper/Section.cs index 40aa3b6f..d7be21bd 100644 --- a/FModel/Views/Snooper/Section.cs +++ b/FModel/Views/Snooper/Section.cs @@ -17,12 +17,10 @@ public class Section : IDisposable private GL _gl; private Vector3 _ambientLight; - private Vector3 _diffuseLight; - private Vector3 _specularLight; private readonly FGame _game; - public readonly string Name; + public string Name; public readonly int Index; public readonly uint FacesCount; public readonly int FirstFaceIndex; @@ -58,13 +56,18 @@ public class Section : IDisposable { if (section.Material != null && section.Material.TryLoad(out var material) && material is UMaterialInterface unrealMaterial) { - Name = unrealMaterial.Name; - unrealMaterial.GetParams(Parameters); + SwapMaterial(unrealMaterial); } } - public Section(string name, int index, uint facesCount, int firstFaceIndex, UMaterialInterface unrealMaterial) : this(name, index, facesCount, firstFaceIndex) + public Section(int index, uint facesCount, int firstFaceIndex, UMaterialInterface unrealMaterial) : this(string.Empty, index, facesCount, firstFaceIndex) { + SwapMaterial(unrealMaterial); + } + + public void SwapMaterial(UMaterialInterface unrealMaterial) + { + Name = unrealMaterial.Name; unrealMaterial.GetParams(Parameters); } @@ -119,8 +122,6 @@ public class Section : IDisposable // diffuse light is based on normal map, so increase ambient if no normal map _ambientLight = new Vector3(Textures[1] == null ? 1.0f : 0.2f); - _diffuseLight = new Vector3(0.75f); - _specularLight = new Vector3(0.5f); HasSpecularMap = Textures[2] != null; HasDiffuseColor = DiffuseColor != Vector4.Zero; Show = !Parameters.IsNull && !Parameters.IsTransparent; @@ -230,7 +231,7 @@ public class Section : IDisposable } } - public void Bind(Shader shader) + public void Bind(Shader shader, uint instanceCount) { for (var i = 0; i < Textures.Length; i++) { @@ -247,11 +248,9 @@ public class Section : IDisposable shader.SetUniform("material.shininess", Parameters.MetallicValue); shader.SetUniform("light.ambient", _ambientLight); - shader.SetUniform("light.diffuse", _diffuseLight); - shader.SetUniform("light.specular", _specularLight); _gl.PolygonMode(MaterialFace.FrontAndBack, Wireframe ? PolygonMode.Line : PolygonMode.Fill); - if (Show) _gl.DrawArrays(PrimitiveType.Triangles, FirstFaceIndex, FacesCount); + if (Show) _gl.DrawArraysInstanced(PrimitiveType.Triangles, FirstFaceIndex, FacesCount, instanceCount); } public void Dispose() diff --git a/FModel/Views/Snooper/SnimGui.cs b/FModel/Views/Snooper/SnimGui.cs index 952bf904..d7d0e42f 100644 --- a/FModel/Views/Snooper/SnimGui.cs +++ b/FModel/Views/Snooper/SnimGui.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Numerics; using System.Windows; +using CUE4Parse.UE4.Objects.Core.Misc; using FModel.Creator; using ImGuiNET; using Silk.NET.Input; @@ -17,7 +18,6 @@ public class SnimGui : IDisposable private readonly ImGuiController _controller; private readonly GraphicsAPI _api; private readonly string _renderer; - private readonly string _version; private readonly Vector2 _outlinerSize; private readonly Vector2 _outlinerPosition; @@ -28,7 +28,8 @@ public class SnimGui : IDisposable private readonly Vector2 _textureSize; private readonly Vector2 _texturePosition; private bool _viewportFocus; - private int _selectedModel; + private FGuid _selectedModel; + private int _selectedInstance; private int _selectedSection; private const ImGuiWindowFlags _noResize = ImGuiWindowFlags.NoResize | ImGuiWindowFlags.NoMove; // delete once we have a proper docking branch @@ -41,13 +42,12 @@ public class SnimGui : IDisposable _controller = new ImGuiController(gl, window, input, fontConfig); _api = window.API; _renderer = gl.GetStringS(StringName.Renderer); - _version = gl.GetStringS(StringName.Version); var style = ImGui.GetStyle(); var viewport = ImGui.GetMainViewport(); var titleBarHeight = ImGui.GetFontSize() + style.FramePadding.Y * 2; - _outlinerSize = new Vector2(400, 250); + _outlinerSize = new Vector2(400, 300); _outlinerPosition = new Vector2(viewport.WorkSize.X - _outlinerSize.X, titleBarHeight); _propertiesSize = _outlinerSize with { Y = viewport.WorkSize.Y - _outlinerSize.Y - titleBarHeight }; _propertiesPosition = new Vector2(viewport.WorkSize.X - _propertiesSize.X, _outlinerPosition.Y + _outlinerSize.Y); @@ -55,13 +55,16 @@ public class SnimGui : IDisposable _viewportPosition = new Vector2(0, titleBarHeight); _textureSize = _viewportSize with { Y = viewport.WorkSize.Y - _viewportSize.Y - titleBarHeight }; _texturePosition = new Vector2(0, _viewportPosition.Y + _viewportSize.Y); - _selectedModel = 0; + _selectedModel = new FGuid(); + _selectedInstance = 0; _selectedSection = 0; Theme(style); } - public void Construct(Vector2D size, FramebufferObject framebuffer, Camera camera, IMouse mouse, IList models) + public void Increment(FGuid guid) => _selectedModel = guid; + + public void Construct(Vector2D size, FramebufferObject framebuffer, Camera camera, IMouse mouse, IDictionary models) { DrawDockSpace(size); DrawNavbar(); @@ -119,35 +122,38 @@ public class SnimGui : IDisposable ImGui.EndMainMenuBar(); } - private void DrawOuliner(Camera camera, IList models) + private void DrawOuliner(Camera camera, IDictionary models) { ImGui.SetNextWindowSize(_outlinerSize, _firstUse); ImGui.SetNextWindowPos(_outlinerPosition, _firstUse); ImGui.Begin("Scene", _noResize | ImGuiWindowFlags.NoCollapse); - ImGui.Text($"Platform: {_api.API} {_api.Profile} {_api.Version.MajorVersion}.{_api.Version.MinorVersion}"); - ImGui.Text($"Renderer: {_renderer}"); - ImGui.Text($"Version: {_version}"); - ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); if (ImGui.TreeNode("Collection")) { - for (var i = 0; i < models.Count; i++) + var i = 0; + foreach (var (guid, model) in models) { - var model = models[i]; ImGui.PushID(i); - if (ImGui.Selectable(model.Name, _selectedModel == i)) - _selectedModel = i; + if (ImGui.Selectable(model.Name, _selectedModel == guid)) + { + _selectedModel = guid; + _selectedInstance = 0; + _selectedSection = 0; + } if (ImGui.BeginPopupContextItem()) { if (ImGui.Selectable("Delete")) - { - _selectedModel--; - models.RemoveAt(i); - } + models.Remove(guid); + if (ImGui.Selectable("Copy to Clipboard")) + Application.Current.Dispatcher.Invoke(delegate + { + Clipboard.SetText(model.Name); + }); ImGui.EndPopup(); } ImGui.PopID(); + i++; } ImGui.TreePop(); } @@ -166,16 +172,22 @@ public class SnimGui : IDisposable ImGui.End(); } - private void DrawProperties(Camera camera, IList models) + private void DrawProperties(Camera camera, IDictionary models) { ImGui.SetNextWindowSize(_propertiesSize, _firstUse); ImGui.SetNextWindowPos(_propertiesPosition, _firstUse); ImGui.Begin("Properties", _noResize | ImGuiWindowFlags.NoCollapse); - if (_selectedModel < 0) return; - var model = models[_selectedModel]; + if (!models.TryGetValue(_selectedModel, out var model)) return; ImGui.Text($"Type: {model.Type}"); ImGui.Text($"Entity: {model.Name}"); + ImGui.Separator(); + if (ImGui.Button("Focus")) + camera.Position = model.Transforms[_selectedInstance].Position; + ImGui.SameLine(); + ImGui.BeginDisabled(model.TransformsCount < 2); + ImGui.SliderInt("Instance", ref _selectedInstance, 0, model.TransformsCount - 1, "%i", ImGuiSliderFlags.AlwaysClamp); + ImGui.EndDisabled(); ImGui.BeginDisabled(!model.HasVertexColors); ImGui.Checkbox("Vertex Colors", ref model.DisplayVertexColors); ImGui.EndDisabled(); @@ -190,45 +202,46 @@ public class SnimGui : IDisposable var index = 0; ImGui.SetNextItemWidth(width); ImGui.PushID(index); - ImGui.DragFloat(model.TransformsLabels[index], ref model.Transforms.Position.X, speed, 0f, 0f, "%.2f m"); + ImGui.DragFloat(model.TransformsLabels[index], ref model.Transforms[_selectedInstance].Position.X, speed, 0f, 0f, "%.2f m"); ImGui.PopID(); index++; ImGui.SetNextItemWidth(width); ImGui.PushID(index); - ImGui.DragFloat(model.TransformsLabels[index], ref model.Transforms.Position.Z, speed, 0f, 0f, "%.2f m"); + ImGui.DragFloat(model.TransformsLabels[index], ref model.Transforms[_selectedInstance].Position.Y, speed, 0f, 0f, "%.2f m"); ImGui.PopID(); index++; ImGui.SetNextItemWidth(width); ImGui.PushID(index); - ImGui.DragFloat(model.TransformsLabels[index], ref model.Transforms.Position.Y, speed, 0f, 0f, "%.2f m"); + ImGui.DragFloat(model.TransformsLabels[index], ref model.Transforms[_selectedInstance].Position.Z, speed, 0f, 0f, "%.2f m"); ImGui.PopID(); ImGui.Spacing(); index++; ImGui.SetNextItemWidth(width); ImGui.PushID(index); - ImGui.DragFloat(model.TransformsLabels[index], ref model.Transforms.Rotation.X, 1f, 0f, 0f, "%.1f°"); + ImGui.DragFloat(model.TransformsLabels[index], ref model.Transforms[_selectedInstance].Rotation.Pitch, .5f, 0f, 0f, "%.1f°"); ImGui.PopID(); index++; ImGui.SetNextItemWidth(width); ImGui.PushID(index); - ImGui.DragFloat(model.TransformsLabels[index], ref model.Transforms.Rotation.Z, 1f, 0f, 0f, "%.1f°"); + ImGui.DragFloat(model.TransformsLabels[index], ref model.Transforms[_selectedInstance].Rotation.Roll, .5f, 0f, 0f, "%.1f°"); ImGui.PopID(); index++; ImGui.SetNextItemWidth(width); ImGui.PushID(index); - ImGui.DragFloat(model.TransformsLabels[index], ref model.Transforms.Rotation.Y, 1f, 0f, 0f, "%.1f°"); + ImGui.DragFloat(model.TransformsLabels[index], ref model.Transforms[_selectedInstance].Rotation.Yaw, .5f, 0f, 0f, "%.1f°"); ImGui.PopID(); ImGui.Spacing(); index++; ImGui.SetNextItemWidth(width); ImGui.PushID(index); - ImGui.DragFloat(model.TransformsLabels[index], ref model.Transforms.Scale.X, speed, 0f, 0f, "%.3f"); + ImGui.DragFloat(model.TransformsLabels[index], ref model.Transforms[_selectedInstance].Scale.X, speed, 0f, 0f, "%.3f"); ImGui.PopID(); index++; ImGui.SetNextItemWidth(width); ImGui.PushID(index); - ImGui.DragFloat(model.TransformsLabels[index], ref model.Transforms.Scale.Z, speed, 0f, 0f, "%.3f"); + ImGui.DragFloat(model.TransformsLabels[index], ref model.Transforms[_selectedInstance].Scale.Y, speed, 0f, 0f, "%.3f"); ImGui.PopID(); index++; ImGui.SetNextItemWidth(width); ImGui.PushID(index); - ImGui.DragFloat(model.TransformsLabels[index], ref model.Transforms.Scale.Y, speed, 0f, 0f, "%.3f"); + ImGui.DragFloat(model.TransformsLabels[index], ref model.Transforms[_selectedInstance].Scale.Z, speed, 0f, 0f, "%.3f"); ImGui.PopID(); + model.UpdateMatrix(_selectedInstance); ImGui.TreePop(); } @@ -245,11 +258,11 @@ public class SnimGui : IDisposable ImGui.PushID(i); ImGui.TableNextRow(); + ImGui.TableNextColumn(); if (!section.Show) ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, ImGui.GetColorU32(new Vector4(1, 0, 0, .5f))); - ImGui.TableSetColumnIndex(0); ImGui.Text(section.Index.ToString("D")); - ImGui.TableSetColumnIndex(1); + ImGui.TableNextColumn(); if (ImGui.Selectable(section.Name, _selectedSection == i, ImGuiSelectableFlags.SpanAllColumns)) _selectedSection = i; ImGui.PopID(); @@ -261,14 +274,14 @@ public class SnimGui : IDisposable ImGui.End(); } - private void DrawTextures(IList models) + private void DrawTextures(IDictionary models) { ImGui.SetNextWindowSize(_textureSize, _firstUse); ImGui.SetNextWindowPos(_texturePosition, _firstUse); ImGui.Begin("Textures", _noResize | ImGuiWindowFlags.NoCollapse); - if (_selectedModel < 0) return; - var section = models[_selectedModel].Sections[_selectedSection]; + if (!models.TryGetValue(_selectedModel, out var model)) return; + var section = model.Sections[_selectedSection]; ImGui.BeginGroup(); ImGui.Checkbox("Show", ref section.Show); ImGui.Checkbox("Wireframe", ref section.Wireframe); @@ -356,7 +369,7 @@ public class SnimGui : IDisposable ImGui.SetNextWindowSize(_viewportSize, _firstUse); ImGui.SetNextWindowPos(_viewportPosition, _firstUse); ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero); - ImGui.Begin("Viewport", _noResize | flags); + ImGui.Begin($"Viewport ({_api.API} {_api.Version.MajorVersion}.{_api.Version.MinorVersion}) ({_renderer})", _noResize | flags); ImGui.PopStyleVar(); var largest = ImGui.GetContentRegionAvail(); @@ -425,6 +438,7 @@ public class SnimGui : IDisposable io.ConfigDockingWithShift = true; style.WindowMenuButtonPosition = ImGuiDir.Right; + style.ScrollbarSize = 10f; // style.Colors[(int) ImGuiCol.Text] = new Vector4(0.95f, 0.96f, 0.98f, 1.00f); // style.Colors[(int) ImGuiCol.TextDisabled] = new Vector4(0.36f, 0.42f, 0.47f, 1.00f); // style.Colors[(int) ImGuiCol.WindowBg] = new Vector4(0.149f, 0.149f, 0.188f, 0.35f); diff --git a/FModel/Views/Snooper/Snooper.cs b/FModel/Views/Snooper/Snooper.cs index b69cd161..67965e74 100644 --- a/FModel/Views/Snooper/Snooper.cs +++ b/FModel/Views/Snooper/Snooper.cs @@ -7,7 +7,11 @@ using CUE4Parse.UE4.Assets.Exports; using CUE4Parse.UE4.Assets.Exports.Material; using CUE4Parse.UE4.Assets.Exports.SkeletalMesh; using CUE4Parse.UE4.Assets.Exports.StaticMesh; +using CUE4Parse.UE4.Assets.Exports.Texture; using CUE4Parse.UE4.Objects.Core.Math; +using CUE4Parse.UE4.Objects.Core.Misc; +using CUE4Parse.UE4.Objects.Engine; +using CUE4Parse.UE4.Objects.UObject; using CUE4Parse_Conversion.Meshes; using ImGuiNET; using Silk.NET.Core; @@ -15,9 +19,9 @@ using Silk.NET.Input; using Silk.NET.Maths; using Silk.NET.OpenGL; using Silk.NET.Windowing; -using SixLabors.ImageSharp; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; +using Image = SixLabors.ImageSharp.Image; namespace FModel.Views.Snooper; @@ -34,7 +38,11 @@ public class Snooper private readonly FramebufferObject _framebuffer; private readonly Skybox _skybox; private readonly Grid _grid; - private readonly List _models; + + private Shader _shader; + private Vector3 _diffuseLight; + private Vector3 _specularLight; + private readonly Dictionary _models; private Vector2D _size; private float _previousSpeed; @@ -80,7 +88,7 @@ public class Snooper _framebuffer = new FramebufferObject(_size); _skybox = new Skybox(); _grid = new Grid(); - _models = new List(); + _models = new Dictionary(); } public void Run(UObject export) @@ -89,29 +97,139 @@ public class Snooper { case UStaticMesh st when st.TryConvert(out var mesh): { - _models.Add(new Model(st.Name, st.ExportType, mesh.LODs[0], mesh.LODs[0].Verts)); - SetupCamera(mesh.BoundingBox *= Constants.SCALE_DOWN_RATIO); + var guid = st.LightingGuid; + if (!_models.TryGetValue(guid, out _)) + { + _models[guid] = new Model(st.Name, st.ExportType, mesh.LODs[0], mesh.LODs[0].Verts); + SetupCamera(mesh.BoundingBox *= Constants.SCALE_DOWN_RATIO); + } break; } case USkeletalMesh sk when sk.TryConvert(out var mesh): { - _models.Add(new Model(sk.Name, sk.ExportType, mesh.LODs[0], mesh.LODs[0].Verts, mesh.RefSkeleton)); - SetupCamera(mesh.BoundingBox *= Constants.SCALE_DOWN_RATIO); + var guid = Guid.NewGuid(); + if (!_models.TryGetValue(guid, out _)) + { + _models[guid] = new Model(sk.Name, sk.ExportType, mesh.LODs[0], mesh.LODs[0].Verts, mesh.RefSkeleton); + SetupCamera(mesh.BoundingBox *= Constants.SCALE_DOWN_RATIO); + } break; } case UMaterialInstance mi: { - _models.Add(new Cube(mi.Name, mi.ExportType, mi)); - _camera = new Camera(new Vector3(0f, 0f, 2f), Vector3.Zero, 0.01f, 0.5f * 50f, 0.5f / 2f); + var guid = Guid.NewGuid(); + if (!_models.TryGetValue(guid, out _)) + { + _models[guid] = new Cube(mi.Name, mi.ExportType, mi); + SetupCamera(new FBox(new FVector(-.65f), new FVector(.65f))); + } + break; + } + case UWorld wd: + { + var persistentLevel = wd.PersistentLevel.Load(); + for (var i = 0; i < persistentLevel.Actors.Length; i++) + { + if (persistentLevel.Actors[i].Load() is not { } actor || actor.ExportType == "LODActor" || + !actor.TryGetValue(out FPackageIndex staticMeshComponent, "StaticMeshComponent") || + staticMeshComponent.Load() is not { } staticMeshComp) continue; + + if (!staticMeshComp.TryGetValue(out FPackageIndex staticMesh, "StaticMesh") && actor.Class is UBlueprintGeneratedClass) + { + foreach (var actorExp in actor.Class.Owner.GetExports()) + if (actorExp.TryGetValue(out staticMesh, "StaticMesh")) + break; + } + + if (staticMesh?.Load() is not UStaticMesh m || !m.TryConvert(out var mesh)) + continue; + + var guid = m.LightingGuid; + var transform = new Transform + { + Position = staticMeshComp.GetOrDefault("RelativeLocation", FVector.ZeroVector) * Constants.SCALE_DOWN_RATIO, + Rotation = staticMeshComp.GetOrDefault("RelativeRotation", FRotator.ZeroRotator), + Scale = staticMeshComp.GetOrDefault("RelativeScale3D", FVector.OneVector) + }; + // can't seem to find the problem here + // some meshes should have their yaw reversed and others not + transform.Rotation.Yaw = -transform.Rotation.Yaw; + if (_models.TryGetValue(guid, out var model)) + { + model.AddInstance(transform); + continue; + } + + model = new Model(m.Name, m.ExportType, mesh.LODs[0], mesh.LODs[0].Verts, null, transform); + if (actor.TryGetAllValues(out FPackageIndex[] textureData, "TextureData")) + { + for (int j = 0; j < textureData.Length; j++) + { + if (textureData[j].Load() is not { } textureDataIdx) + continue; + + if (textureDataIdx.TryGetValue(out FPackageIndex diffuse, "Diffuse") && + diffuse.Load() is UTexture2D diffuseTexture) + model.Sections[j].Parameters.Diffuse = diffuseTexture; + if (textureDataIdx.TryGetValue(out FPackageIndex normal, "Normal") && + normal.Load() is UTexture2D normalTexture) + model.Sections[j].Parameters.Normal = normalTexture; + if (textureDataIdx.TryGetValue(out FPackageIndex specular, "Specular") && + specular.Load() is UTexture2D specularTexture) + model.Sections[j].Parameters.Specular = specularTexture; + } + } + if (staticMeshComp.TryGetValue(out FPackageIndex[] overrideMaterials, "OverrideMaterials")) + { + var max = model.Sections.Length - 1; + for (var j = 0; j < overrideMaterials.Length; j++) + { + if (j > max) break; + if (overrideMaterials[j].Load() is not UMaterialInterface unrealMaterial) continue; + model.Sections[j].SwapMaterial(unrealMaterial); + } + } + + _models[guid] = model; + } + _camera = new Camera(new Vector3(0f, 0f, 5f), Vector3.Zero, 0.01f, 1000f, 5f); break; } default: throw new ArgumentOutOfRangeException(nameof(export)); } - // because this calls Reset from an internal class we must recall GL.GetApi(_window) on load - // so basically we can't keep our current scene and we have to setup everything again + DoLoop(); + } + + private void DoLoop() + { + if (_append) _append = false; _window.Run(); + // if (_window.IsInitialized) + // { + // if (!_window.GLContext.IsCurrent) + // { + // // huston we have a problem + // // this is apparently a bug + // } + // _models[^1].Setup(_gl); + // _imGui.Increment(); + // } + // else _window.Initialize(); + // + // while (!_window.IsClosing && _window.IsVisible) + // { + // _window.DoEvents(); + // if (!_window.IsClosing && _window.IsVisible) + // _window.DoUpdate(); + // if (_window.IsClosing || !_window.IsVisible) + // return; + // _window.DoRender(); + // } + // + // _window.DoEvents(); + // if (_window.IsClosing) _window.Reset(); } private void SetupCamera(FBox box) @@ -120,11 +238,11 @@ public class Snooper var center = box.GetCenter(); var position = new Vector3(0f, center.Z, box.Max.Y * 3); var speed = far / 2f; - - if (_camera == null) + if (speed > _previousSpeed) + { _camera = new Camera(position, center, 0.01f, far * 50f, speed); - else if (speed > _previousSpeed) - _camera.Speed = _previousSpeed = speed; + _previousSpeed = _camera.Speed; + } } private void OnLoad() @@ -145,7 +263,10 @@ public class Snooper _skybox.Setup(_gl); _grid.Setup(_gl); - foreach (var model in _models) + _shader = new Shader(_gl); + _diffuseLight = new Vector3(0.75f); + _specularLight = new Vector3(0.5f); + foreach (var model in _models.Values) { model.Setup(_gl); } @@ -167,9 +288,24 @@ public class Snooper _skybox.Bind(_camera); _grid.Bind(_camera); - foreach (var model in _models) + _shader.Use(); + + _shader.SetUniform("uView", _camera.GetViewMatrix()); + _shader.SetUniform("uProjection", _camera.GetProjectionMatrix()); + _shader.SetUniform("viewPos", _camera.Position); + + _shader.SetUniform("material.diffuseMap", 0); + _shader.SetUniform("material.normalMap", 1); + _shader.SetUniform("material.specularMap", 2); + _shader.SetUniform("material.emissionMap", 3); + + _shader.SetUniform("light.position", _camera.Position); + _shader.SetUniform("light.diffuse", _diffuseLight); + _shader.SetUniform("light.specular", _specularLight); + + foreach (var model in _models.Values) { - model.Bind(_camera); + model.Bind(_shader); } _imGui.Construct(_size, _framebuffer, _camera, _mouse, _models); @@ -211,6 +347,10 @@ public class Snooper if (_keyboard.IsKeyPressed(Key.H)) { + // because we lose GLContext when the window is invisible after a few seconds (it's apparently a bug) + // we can't use GLContext back on next load and so, for now, we basically have to reset the window + // if we can't use GLContext, we can't generate handles, can't interact with IsVisible, State, etc + // tldr we dispose everything but don't clear models, so the more you append, the longer it takes to load _append = true; _window.Close(); } @@ -223,7 +363,8 @@ public class Snooper _framebuffer.Dispose(); _grid.Dispose(); _skybox.Dispose(); - foreach (var model in _models) + _shader.Dispose(); + foreach (var model in _models.Values) { model.Dispose(); } diff --git a/FModel/Views/Snooper/Texture.cs b/FModel/Views/Snooper/Texture.cs index 630f09d8..dbcfe477 100644 --- a/FModel/Views/Snooper/Texture.cs +++ b/FModel/Views/Snooper/Texture.cs @@ -79,7 +79,7 @@ public class Texture : IDisposable fixed (void* d = &data[0]) { - _gl.TexImage2D(TextureTarget.Texture2D, 0, (int) InternalFormat.Rgba, Width, Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, d); + _gl.TexImage2D(TextureTarget.Texture2D, 0, (int) InternalFormat.Rgb, Width, Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, d); _gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int) GLEnum.LinearMipmapLinear); _gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int) GLEnum.Linear); _gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureBaseLevel, 0); diff --git a/FModel/Views/Snooper/Transform.cs b/FModel/Views/Snooper/Transform.cs index 9fb4476b..1b748081 100644 --- a/FModel/Views/Snooper/Transform.cs +++ b/FModel/Views/Snooper/Transform.cs @@ -1,4 +1,5 @@ using System.Numerics; +using CUE4Parse.UE4.Objects.Core.Math; namespace FModel.Views.Snooper; @@ -9,16 +10,16 @@ public class Transform get => new (); } - public Vector3 Position = Vector3.Zero; - public Vector3 Rotation = Vector3.Zero; - public Vector3 Scale = Vector3.One; + public FVector Position = FVector.ZeroVector; + public FRotator Rotation = FRotator.ZeroRotator; + public FVector Scale = FVector.OneVector; public Matrix4x4 Matrix => Matrix4x4.Identity * Matrix4x4.CreateFromYawPitchRoll( - Helper.DegreesToRadians(Rotation.Y), - Helper.DegreesToRadians(Rotation.X), - Helper.DegreesToRadians(Rotation.Z)) * + Helper.DegreesToRadians(Rotation.Yaw), + Helper.DegreesToRadians(Rotation.Pitch), + Helper.DegreesToRadians(Rotation.Roll)) * Matrix4x4.CreateScale(Scale) * Matrix4x4.CreateTranslation(Position); } diff --git a/FModel/Views/Snooper/VertexArrayObject.cs b/FModel/Views/Snooper/VertexArrayObject.cs index e4fc99dd..023e32cd 100644 --- a/FModel/Views/Snooper/VertexArrayObject.cs +++ b/FModel/Views/Snooper/VertexArrayObject.cs @@ -1,4 +1,5 @@ using System; +using System.Numerics; using Silk.NET.OpenGL; namespace FModel.Views.Snooper; @@ -37,6 +38,29 @@ public class VertexArrayObject : IDisposable where TVer _gl.BindVertexArray(_handle); } + public unsafe void BindInstancing() + { + var vec4Size = (uint) sizeof(Vector4); + _gl.EnableVertexAttribArray(6); + _gl.VertexAttribPointer(6, 4, VertexAttribPointerType.Float, false, 4 * vec4Size, (void*)0); + _gl.EnableVertexAttribArray(7); + _gl.VertexAttribPointer(7, 4, VertexAttribPointerType.Float, false, 4 * vec4Size, (void*)(1 * vec4Size)); + _gl.EnableVertexAttribArray(8); + _gl.VertexAttribPointer(8, 4, VertexAttribPointerType.Float, false, 4 * vec4Size, (void*)(2 * vec4Size)); + _gl.EnableVertexAttribArray(9); + _gl.VertexAttribPointer(9, 4, VertexAttribPointerType.Float, false, 4 * vec4Size, (void*)(3 * vec4Size)); + + _gl.VertexAttribDivisor(6, 1); + _gl.VertexAttribDivisor(7, 1); + _gl.VertexAttribDivisor(8, 1); + _gl.VertexAttribDivisor(9, 1); + } + + public void Unbind() + { + _gl.BindVertexArray(0); + } + public void Dispose() { _gl.DeleteVertexArray(_handle);