diff --git a/CUE4Parse b/CUE4Parse index f71be1eb..5987bbdb 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit f71be1eb998ee38e94a1a77994fb21e0338905c1 +Subproject commit 5987bbdb4e094c8c08e7b3cf05fc6ad6728299fb diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index 43678334..eba09a98 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -873,11 +873,11 @@ public class CUE4ParseViewModel : ViewModel }; var toSave = new Exporter(export, exportOptions); var toSaveDirectory = new DirectoryInfo(UserSettings.Default.ModelDirectory); - if (toSave.TryWriteToDir(toSaveDirectory, out var savedFileName)) + if (toSave.TryWriteToDir(toSaveDirectory, out var label, out var savedFilePath)) { - Log.Information("Successfully saved {FileName}", savedFileName); + Log.Information("Successfully saved {FilePath}", savedFilePath); FLogger.AppendInformation(); - FLogger.AppendText($"Successfully saved {savedFileName}", Constants.WHITE, true); + FLogger.AppendText($"Successfully saved {label}", Constants.WHITE, true); } else { diff --git a/FModel/Views/Snooper/Cube.cs b/FModel/Views/Snooper/Cube.cs index db53b9c6..f9275221 100644 --- a/FModel/Views/Snooper/Cube.cs +++ b/FModel/Views/Snooper/Cube.cs @@ -4,7 +4,7 @@ namespace FModel.Views.Snooper; public class Cube : Model { - public Cube(UMaterialInterface unrealMaterial) : base(unrealMaterial.Name, unrealMaterial.ExportType) + public Cube(UMaterialInterface unrealMaterial) : base(unrealMaterial) { Indices = new uint[] { diff --git a/FModel/Views/Snooper/Material.cs b/FModel/Views/Snooper/Material.cs index 0a76f34b..6bf7f7dc 100644 --- a/FModel/Views/Snooper/Material.cs +++ b/FModel/Views/Snooper/Material.cs @@ -259,6 +259,15 @@ public class Material : IDisposable } } + public void ImGuiColors(Dictionary colors) + { + foreach ((string key, FLinearColor value) in colors.Reverse()) + { + ImGui.ColorButton(key, new Vector4(value.R, value.G, value.B, value.A), ImGuiColorEditFlags.None, new Vector2(16)); + ImGui.SameLine();ImGui.Text(key);SnimGui.TooltipCopy(key); + } + } + public void ImGuiTextures(Dictionary icons, Model model) { if (ImGui.BeginTable("material_textures", 2)) @@ -286,7 +295,9 @@ public class Material : IDisposable ImGui.EndTable(); } - ImGui.Image(GetSelectedTexture() ?? icons["noimage"].GetPointer(), new Vector2(ImGui.GetContentRegionAvail().X), Vector2.Zero, Vector2.One, Vector4.One, new Vector4(1.0f, 1.0f, 1.0f, 0.25f)); + ImGui.Image(GetSelectedTexture() ?? icons["noimage"].GetPointer(), + new Vector2(ImGui.GetContentRegionAvail().X - ImGui.GetScrollX()), + Vector2.Zero, Vector2.One, Vector4.One, new Vector4(1.0f, 1.0f, 1.0f, 0.25f)); ImGui.Spacing(); } diff --git a/FModel/Views/Snooper/Model.cs b/FModel/Views/Snooper/Model.cs index 447fa4ea..f3a59175 100644 --- a/FModel/Views/Snooper/Model.cs +++ b/FModel/Views/Snooper/Model.cs @@ -1,12 +1,20 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Numerics; +using CUE4Parse_Conversion; using CUE4Parse.UE4.Assets.Exports.Animation; using CUE4Parse.UE4.Objects.UObject; using CUE4Parse_Conversion.Meshes.PSK; using CUE4Parse.UE4.Assets; +using CUE4Parse.UE4.Assets.Exports; using CUE4Parse.UE4.Assets.Exports.Material; +using CUE4Parse.UE4.Assets.Exports.SkeletalMesh; +using CUE4Parse.UE4.Assets.Exports.StaticMesh; +using FModel.Extensions; +using FModel.Services; +using FModel.Settings; using OpenTK.Graphics.OpenGL4; namespace FModel.Views.Snooper; @@ -21,10 +29,12 @@ public class Model : IDisposable private BufferObject _matrixVbo; private VertexArrayObject _vao; + private readonly UObject _export; private readonly int _vertexSize = 13; // VertexIndex + Position + Normal + Tangent + UV + TextureLayer private readonly uint[] _facesIndex = { 1, 0, 2 }; private const int _faceSize = 3; // just so we don't have to do .Length + public readonly string Path; public readonly string Name; public readonly string Type; public readonly bool HasVertexColors; @@ -50,20 +60,22 @@ public class Model : IDisposable public int SelectedInstance; public float MorphTime; - protected Model(string name, string type) + protected Model(UObject export) { - Name = name; - Type = type; + _export = export; + Path = _export.GetPathName(); + Name = Path.SubstringAfterLast('/').SubstringBefore('.'); + Type = export.ExportType; NumTexCoords = 1; Transforms = new List(); } - public Model(string name, string type, ResolvedObject[] materials, CStaticMesh staticMesh) : this(name, type, materials, staticMesh, Transform.Identity) {} - public Model(string name, string type, ResolvedObject[] materials, CStaticMesh staticMesh, Transform transform) : this(name, type, materials, null, staticMesh.LODs[0], staticMesh.LODs[0].Verts, transform) {} - public Model(string name, string type, ResolvedObject[] materials, FPackageIndex skeleton, CSkeletalMesh skeletalMesh) : this(name, type, materials, skeleton, skeletalMesh, Transform.Identity) {} - public Model(string name, string type, ResolvedObject[] materials, FPackageIndex skeleton, CSkeletalMesh skeletalMesh, Transform transform) : this(name, type, materials, skeleton, skeletalMesh.LODs[0], skeletalMesh.LODs[0].Verts, transform) {} - public Model(string name, string type, ResolvedObject[] materials, FPackageIndex skeleton, FPackageIndex[] morphTargets, CSkeletalMesh skeletalMesh) : this(name, type, materials, skeleton, skeletalMesh) + public Model(UStaticMesh export, CStaticMesh staticMesh) : this(export, staticMesh, Transform.Identity) {} + public Model(UStaticMesh export, CStaticMesh staticMesh, Transform transform) : this(export, export.Materials, null, staticMesh.LODs[0], staticMesh.LODs[0].Verts, transform) {} + private Model(USkeletalMesh export, CSkeletalMesh skeletalMesh, Transform transform) : this(export, export.Materials, export.Skeleton, skeletalMesh.LODs[0], skeletalMesh.LODs[0].Verts, transform) {} + public Model(USkeletalMesh export, CSkeletalMesh skeletalMesh) : this(export, skeletalMesh, Transform.Identity) { + var morphTargets = export.MorphTargets; if (morphTargets is not { Length: > 0 }) return; @@ -74,12 +86,12 @@ public class Model : IDisposable for (var i = 0; i < Morphs.Length; i++) { Morphs[i] = new Morph(Vertices, _vertexSize, morphTargets[i].Load()); - Services.ApplicationService.ApplicationView.Status.UpdateStatusLabel($"{Morphs[i].Name} ... {i}/{length}"); + ApplicationService.ApplicationView.Status.UpdateStatusLabel($"{Morphs[i].Name} ... {i}/{length}"); } - Services.ApplicationService.ApplicationView.Status.UpdateStatusLabel(""); + ApplicationService.ApplicationView.Status.UpdateStatusLabel(""); } - private Model(string name, string type, ResolvedObject[] materials, FPackageIndex skeleton, CBaseMeshLod lod, CMeshVertex[] vertices, Transform transform = null) : this(name, type) + private Model(UObject export, ResolvedObject[] materials, FPackageIndex skeleton, CBaseMeshLod lod, CMeshVertex[] vertices, Transform transform = null) : this(export) { NumTexCoords = lod.NumTexCoords; @@ -305,6 +317,20 @@ public class Model : IDisposable GL.Disable(EnableCap.StencilTest); } + public bool TrySave(out string label, out string savedFilePath) + { + var exportOptions = new ExporterOptions + { + TextureFormat = UserSettings.Default.TextureExportFormat, + LodFormat = UserSettings.Default.LodExportFormat, + MeshFormat = UserSettings.Default.MeshExportFormat, + Platform = UserSettings.Default.OverridedPlatform, + ExportMorphTargets = UserSettings.Default.SaveMorphTargets + }; + var toSave = new Exporter(_export, exportOptions); + return toSave.TryWriteToDir(new DirectoryInfo(UserSettings.Default.ModelDirectory), out label, out savedFilePath); + } + public void Dispose() { _ebo.Dispose(); diff --git a/FModel/Views/Snooper/Renderer.cs b/FModel/Views/Snooper/Renderer.cs index 1b6cfde0..5eb93c49 100644 --- a/FModel/Views/Snooper/Renderer.cs +++ b/FModel/Views/Snooper/Renderer.cs @@ -157,7 +157,7 @@ public class Renderer : IDisposable if (!original.TryConvert(out var mesh)) return null; - Options.Models[guid] = new Model(original.Name, original.ExportType, original.Materials, mesh); + Options.Models[guid] = new Model(original, mesh); Options.SelectModel(guid); return SetupCamera(mesh.BoundingBox *= Constants.SCALE_DOWN_RATIO); } @@ -167,7 +167,7 @@ public class Renderer : IDisposable var guid = Guid.NewGuid(); if (Options.Models.ContainsKey(guid) || !original.TryConvert(out var mesh)) return null; - Options.Models[guid] = new Model(original.Name, original.ExportType, original.Materials, original.Skeleton, original.MorphTargets, mesh); + Options.Models[guid] = new Model(original, mesh); Options.SelectModel(guid); return SetupCamera(mesh.BoundingBox *= Constants.SCALE_DOWN_RATIO); } @@ -231,7 +231,7 @@ public class Renderer : IDisposable private void WorldMesh(UObject actor, Transform transform) { - if (!actor.TryGetValue(out FPackageIndex staticMeshComponent, "StaticMeshComponent", "Mesh", "LightMesh") || + if (!actor.TryGetValue(out FPackageIndex staticMeshComponent, "StaticMeshComponent", "StaticMesh", "Mesh", "LightMesh") || staticMeshComponent.Load() is not { } staticMeshComp) return; if (!staticMeshComp.TryGetValue(out FPackageIndex staticMesh, "StaticMesh") && actor.Class is UBlueprintGeneratedClass) @@ -257,7 +257,7 @@ public class Renderer : IDisposable } else if (m.TryConvert(out var mesh)) { - model = new Model(m.Name, m.ExportType, m.Materials, mesh, t); + model = new Model(m, mesh, t); if (actor.TryGetAllValues(out FPackageIndex[] textureData, "TextureData")) { for (int j = 0; j < textureData.Length; j++) diff --git a/FModel/Views/Snooper/SnimGui.cs b/FModel/Views/Snooper/SnimGui.cs index 2e00de36..0cb4854d 100644 --- a/FModel/Views/Snooper/SnimGui.cs +++ b/FModel/Views/Snooper/SnimGui.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using CUE4Parse.UE4.Objects.Core.Misc; using FModel.Framework; using ImGuiNET; @@ -8,17 +9,42 @@ using System.Numerics; namespace FModel.Views.Snooper; +public class Swap +{ + public bool Value; + public bool IsAware; + + public void Reset() + { + Value = false; + } +} + +public class Save +{ + public bool Value; + public string Label; + public string Path; + + public void Reset() + { + Value = false; + Label = string.Empty; + Path = string.Empty; + } +} + public class SnimGui { public readonly ImGuiController Controller; private bool _viewportFocus; - private bool _swapAwareness; + private readonly Swap _swapper = new (); + private readonly Save _saver = new (); private readonly Vector4 _accentColor = new (0.125f, 0.42f, 0.831f, 1.0f); private readonly Vector4 _alertColor = new (0.831f, 0.573f, 0.125f, 1.0f); private readonly Vector4 _errorColor = new (0.761f, 0.169f, 0.169f, 1.0f); - private const ImGuiCond _firstUse = ImGuiCond.FirstUseEver; // switch to FirstUseEver once the docking branch will not be useful anymore... private const uint _dockspaceId = 1337; public SnimGui(int width, int height) @@ -132,7 +158,7 @@ public class SnimGui const string text = "Press ESC to Exit..."; ImGui.SetCursorPosX(ImGui.GetWindowViewport().WorkSize.X - ImGui.CalcTextSize(text).X - 5); - ImGui.TextColored(new Vector4(0.36f, 0.42f, 0.47f, 1.00f), text); // ImGuiCol.TextDisabled + ImGui.TextColored(new Vector4(0.36f, 0.42f, 0.47f, 1.00f), text); ImGui.EndMainMenuBar(); } @@ -176,9 +202,12 @@ public class SnimGui ImGui.Separator(); if (ImGui.Selectable("Save")) { - + s.WindowShouldFreeze(true); + _saver.Value = model.TrySave(out _saver.Label, out _saver.Path); + s.WindowShouldFreeze(false); } - ImGui.BeginDisabled(!model.HasSkeleton); + ImGui.BeginDisabled(true); + // ImGui.BeginDisabled(!model.HasSkeleton); if (ImGui.Selectable("Animate")) { s.Renderer.Options.AnimateMesh(true); @@ -200,6 +229,30 @@ public class SnimGui i++; } + Popup("Saved", _saver.Value, () => + { + ImGui.TextWrapped($"Successfully saved {_saver.Label}"); + ImGui.Separator(); + + var size = new Vector2(120, 0); + if (ImGui.Button("OK", size)) + { + _saver.Reset(); + ImGui.CloseCurrentPopup(); + } + + ImGui.SetItemDefaultFocus(); + ImGui.SameLine(); + + if (ImGui.Button("Show In Explorer", size)) + { + Process.Start("explorer.exe", $"/select, \"{_saver.Path.Replace('/', '\\')}\""); + + _saver.Reset(); + ImGui.CloseCurrentPopup(); + } + }); + ImGui.EndTable(); } }); @@ -260,7 +313,6 @@ public class SnimGui ImGui.TableSetupColumn("Material"); ImGui.TableHeadersRow(); - var swap = false; for (var i = 0; i < model.Sections.Length; i++) { var section = model.Sections[i]; @@ -286,12 +338,12 @@ public class SnimGui if (ImGui.MenuItem("Show", null, section.Show)) section.Show = !section.Show; if (ImGui.Selectable("Swap")) { - if (_swapAwareness) + if (_swapper.IsAware) { s.Renderer.Options.SwapMaterial(true); s.WindowShouldClose(true, false); } - else swap = true; + else _swapper.Value = true; } ImGui.Separator(); if (ImGui.Selectable("Copy Name to Clipboard")) ImGui.SetClipboardText(material.Name); @@ -301,23 +353,21 @@ public class SnimGui } ImGui.EndTable(); - var p_open = true; - if (swap) ImGui.OpenPopup("Swap?"); - ImGui.SetNextWindowPos(ImGui.GetMainViewport().GetCenter(), ImGuiCond.Appearing, new Vector2(.5f)); - if (ImGui.BeginPopupModal("Swap?", ref p_open, ImGuiWindowFlags.AlwaysAutoResize)) + Popup("Swap?", _swapper.Value, () => { ImGui.TextWrapped("You're about to swap a material.\nThe window will close for you to extract a material!\n\n"); ImGui.Separator(); ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero); - ImGui.Checkbox("Got it! Don't show me again", ref _swapAwareness); + ImGui.Checkbox("Got it! Don't show me again", ref _swapper.IsAware); ImGui.PopStyleVar(); var size = new Vector2(120, 0); if (ImGui.Button("OK", size)) { - ImGui.CloseCurrentPopup(); + _swapper.Reset(); s.Renderer.Options.SwapMaterial(true); + ImGui.CloseCurrentPopup(); s.WindowShouldClose(true, false); } @@ -326,14 +376,14 @@ public class SnimGui if (ImGui.Button("Cancel", size)) { + _swapper.Reset(); ImGui.CloseCurrentPopup(); } - - ImGui.EndPopup(); - } + }); ImGui.EndTabItem(); } + if (ImGui.BeginTabItem("Transform")) { const int width = 100; @@ -397,6 +447,7 @@ public class SnimGui model.UpdateMatrix(model.SelectedInstance); ImGui.EndTabItem(); } + if (ImGui.BeginTabItem("Morph Targets")) { if (model.HasMorphTargets) @@ -426,7 +477,7 @@ public class SnimGui ImGui.Text($"Time: {model.MorphTime:P}%"); } } - else ImGui.TextColored(_errorColor, "mesh has no morph targets"); + else CenteredTextColored(_errorColor, "Selected Mesh Has No Morph Targets"); ImGui.EndTabItem(); } } @@ -473,7 +524,7 @@ public class SnimGui ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); if (ImGui.TreeNode("Colors")) { - material.ImGuiDictionaries("colors", material.Parameters.Colors, true); + material.ImGuiColors(material.Parameters.Colors); ImGui.TreePop(); } if (ImGui.TreeNode("Referenced Textures")) @@ -535,6 +586,18 @@ public class SnimGui ImGui.PopStyleVar(); } + private void Popup(string title, bool condition, Action content) + { + var pOpen = true; + if (condition) ImGui.OpenPopup(title); + ImGui.SetNextWindowPos(ImGui.GetMainViewport().GetCenter(), ImGuiCond.Appearing, new Vector2(.5f)); + if (ImGui.BeginPopupModal(title, ref pOpen, ImGuiWindowFlags.AlwaysAutoResize)) + { + content(); + ImGui.EndPopup(); + } + } + private void Window(string name, Action content, bool styled = true) { if (ImGui.Begin(name, ImGuiWindowFlags.NoScrollbar)) @@ -682,7 +745,7 @@ public class SnimGui style.Colors[(int) ImGuiCol.TabHovered] = new Vector4(0.35f, 0.35f, 0.41f, 0.80f); style.Colors[(int) ImGuiCol.TabActive] = new Vector4(0.23f, 0.24f, 0.29f, 1.00f); style.Colors[(int) ImGuiCol.TabUnfocused] = new Vector4(0.15f, 0.15f, 0.15f, 1.00f); - style.Colors[(int) ImGuiCol.TabUnfocusedActive] = new Vector4(0.15f, 0.15f, 0.15f, 1.00f); + style.Colors[(int) ImGuiCol.TabUnfocusedActive] = new Vector4(0.23f, 0.24f, 0.29f, 1.00f); style.Colors[(int) ImGuiCol.DockingPreview] = new Vector4(0.26f, 0.59f, 0.98f, 0.70f); style.Colors[(int) ImGuiCol.DockingEmptyBg] = new Vector4(0.20f, 0.20f, 0.20f, 1.00f); style.Colors[(int) ImGuiCol.PlotLines] = new Vector4(0.61f, 0.61f, 0.61f, 1.00f); diff --git a/FModel/Views/Snooper/Snooper.cs b/FModel/Views/Snooper/Snooper.cs index 4a330e43..8b455a5b 100644 --- a/FModel/Views/Snooper/Snooper.cs +++ b/FModel/Views/Snooper/Snooper.cs @@ -59,6 +59,12 @@ public class Snooper : GameWindow IsVisible = !value; } + public unsafe void WindowShouldFreeze(bool value) + { + GLFW.SetWindowShouldClose(WindowPtr, value); // start / stop game loop + IsVisible = true; + } + public override void Run() { Application.Current.Dispatcher.Invoke(delegate