diff --git a/CUE4Parse b/CUE4Parse index 2623c0ba..4ac22af1 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit 2623c0baa171d05f6f6d3922a1a6ea3ae675fb25 +Subproject commit 4ac22af1ff164287f936730bec57261f997ce734 diff --git a/FModel/Views/Snooper/Animations/Animation.cs b/FModel/Views/Snooper/Animations/Animation.cs index f47534cf..037e8cd8 100644 --- a/FModel/Views/Snooper/Animations/Animation.cs +++ b/FModel/Views/Snooper/Animations/Animation.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Numerics; using CUE4Parse_Conversion.Animations; +using CUE4Parse.UE4.Assets.Exports; using CUE4Parse.UE4.Objects.Core.Misc; using CUE4Parse.Utils; using ImGuiNET; @@ -10,6 +11,8 @@ namespace FModel.Views.Snooper.Animations; public class Animation : IDisposable { + public readonly UObject Export; + public readonly string Path; public readonly string Name; public readonly Sequence[] Sequences; public readonly float StartTime; // Animation Start Time @@ -25,15 +28,17 @@ public class Animation : IDisposable public readonly List AttachedModels; - public Animation() + public Animation(UObject export) { + Export = export; + Path = Export.GetPathName(); + Name = Export.Name; Sequences = Array.Empty(); AttachedModels = new List(); } - public Animation(string name, CAnimSet animSet) : this() + public Animation(UObject export, CAnimSet animSet) : this(export) { - Name = name; TargetSkeleton = animSet.OriginalAnim.Name; Sequences = new Sequence[animSet.Sequences.Count]; @@ -49,7 +54,7 @@ public class Animation : IDisposable StartTime = Sequences[0].StartTime; } - public Animation(string name, CAnimSet animSet, params FGuid[] animatedModels) : this(name, animSet) + public Animation(UObject export, CAnimSet animSet, params FGuid[] animatedModels) : this(export, animSet) { AttachedModels.AddRange(animatedModels); } @@ -84,7 +89,7 @@ public class Animation : IDisposable AttachedModels.Clear(); } - public void ImGuiAnimation(ImDrawListPtr drawList, Vector2 timelineP0, Vector2 treeP0, Vector2 treeP1, Vector2 timeStep, Vector2 timeRatio, float y, float t) + public void ImGuiAnimation(Snooper s, Save saver, ImDrawListPtr drawList, Vector2 timelineP0, Vector2 treeP0, Vector2 treeP1, Vector2 timeStep, Vector2 timeRatio, float y, float t, int i) { var p1 = new Vector2(timelineP0.X + StartTime * timeRatio.X + t, y + t); var p2 = new Vector2(timelineP0.X + EndTime * timeRatio.X - t, y + timeStep.Y - t); @@ -92,6 +97,23 @@ public class Animation : IDisposable ImGui.SetCursorScreenPos(p1); ImGui.InvisibleButton($"timeline_sequencetracker_{Name}", new Vector2(EndTime * timeRatio.X - t, timeStep.Y - t), ImGuiButtonFlags.MouseButtonLeft); IsActive = ImGui.IsItemActive(); + IsSelected = s.Renderer.Options.SelectedAnimation == i; + if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) + { + s.Renderer.Options.SelectedAnimation = i; + } + SnimGui.Popup(() => + { + s.Renderer.Options.SelectedAnimation = i; + if (ImGui.Selectable("Save")) + { + s.WindowShouldFreeze(true); + saver.Value = s.Renderer.Options.TrySave(Export, out saver.Label, out saver.Path); + s.WindowShouldFreeze(false); + } + ImGui.Separator(); + if (ImGui.Selectable("Copy Path to Clipboard")) ImGui.SetClipboardText(Path); + }); drawList.AddRectFilled(p1, p2, IsSelected ? 0xFF48B048 : 0xFF175F17, 5.0f, ImDrawFlags.RoundCornersTop); drawList.PushClipRect(p1, p2, true); diff --git a/FModel/Views/Snooper/Animations/Skeleton.cs b/FModel/Views/Snooper/Animations/Skeleton.cs index 11e6ce15..67d2f8e3 100644 --- a/FModel/Views/Snooper/Animations/Skeleton.cs +++ b/FModel/Views/Snooper/Animations/Skeleton.cs @@ -213,7 +213,7 @@ public class Skeleton : IDisposable do { var parentBoneIndices = BonesIndicesByLoweredName[loweredParentBoneName]; - if (parentBoneIndices.HasTrack || parentBoneIndices.HasParentTrack) boneIndices.ParentTrackIndex = parentBoneIndices.BoneIndex; + if (parentBoneIndices.HasParentTrack || parentBoneIndices.IsRoot) boneIndices.ParentTrackIndex = parentBoneIndices.BoneIndex; else loweredParentBoneName = parentBoneIndices.LoweredParentBoneName; } while (!boneIndices.HasParentTrack); } diff --git a/FModel/Views/Snooper/Animations/TimeTracker.cs b/FModel/Views/Snooper/Animations/TimeTracker.cs index dc3a7b1d..636955b5 100644 --- a/FModel/Views/Snooper/Animations/TimeTracker.cs +++ b/FModel/Views/Snooper/Animations/TimeTracker.cs @@ -63,7 +63,7 @@ public class TimeTracker : IDisposable private readonly Vector2 _buttonSize = new (14.0f); private readonly string[] _icons = { "tl_forward", "tl_pause", "tl_rewind" }; - public void ImGuiTimeline(Dictionary icons, List animations, Vector2 outliner, ref int activeAnimation, ImFontPtr fontPtr) + public void ImGuiTimeline(Snooper s, Save saver, Dictionary icons, List animations, Vector2 outliner, ImFontPtr fontPtr) { var treeP0 = ImGui.GetCursorScreenPos(); var canvasSize = ImGui.GetContentRegionAvail(); @@ -94,7 +94,7 @@ public class TimeTracker : IDisposable { var x = _buttonSize.X * 2.0f * i; ImGui.SetCursorScreenPos(treeP0 with { X = treeP1.X - x - _buttonSize.X * 2.0f + _thickness }); - if (ImGui.ImageButton($"timeline_actions_{_icons[i]}", icons[_icons[i]].GetPointer(), _buttonSize)) + if (ImGui.ImageButton($"timeline_actions_{_icons[i]}", icons[i == 1 ? IsPaused ? "tl_play" : "tl_pause" : _icons[i]].GetPointer(), _buttonSize)) { switch (i) { @@ -103,7 +103,6 @@ public class TimeTracker : IDisposable break; case 1: IsPaused = !IsPaused; - _icons[1] = IsPaused ? "tl_play" : "tl_pause"; break; case 2: SafeSetElapsedTime(ElapsedTime - _timeStep.X / timeRatio.X); @@ -112,6 +111,8 @@ public class TimeTracker : IDisposable } } + drawList.AddText(treeP0 with { Y = treeP0.Y + _thickness }, 0xA0FFFFFF, $"{ElapsedTime:F1}/{MaxElapsedTime:F1} seconds"); + ImGui.SetCursorScreenPos(timelineP0); ImGui.InvisibleButton("timeline_timetracker_canvas", timelineSize with { Y = _timeBarHeight }, ImGuiButtonFlags.MouseButtonLeft); IsActive = ImGui.IsItemActive(); @@ -143,12 +144,7 @@ public class TimeTracker : IDisposable for (int i = 0; i < animations.Count; i++) { var y = timelineP0.Y + _timeBarHeight + _timeStep.Y * i; - - animations[i].ImGuiAnimation(drawList, timelineP0, treeP0, treeP1, _timeStep, timeRatio, y, _thickness); - animations[i].IsSelected = activeAnimation == i; - if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) - activeAnimation = i; - + animations[i].ImGuiAnimation(s, saver, drawList, timelineP0, treeP0, treeP1, _timeStep, timeRatio, y, _thickness, i); DrawSeparator(drawList, timelineP0, y + _timeStep.Y, animations[i].EndTime * timeRatio.X, ETrackerType.End); } diff --git a/FModel/Views/Snooper/Models/Model.cs b/FModel/Views/Snooper/Models/Model.cs index 38aa1366..d1785e68 100644 --- a/FModel/Views/Snooper/Models/Model.cs +++ b/FModel/Views/Snooper/Models/Model.cs @@ -1,9 +1,7 @@ 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; @@ -14,7 +12,6 @@ using CUE4Parse.UE4.Assets.Exports.SkeletalMesh; using CUE4Parse.UE4.Assets.Exports.StaticMesh; using CUE4Parse.UE4.Objects.Core.Math; using FModel.Extensions; -using FModel.Settings; using FModel.Views.Snooper.Animations; using FModel.Views.Snooper.Buffers; using FModel.Views.Snooper.Shading; @@ -68,7 +65,7 @@ public class Model : IDisposable public bool HasVertexColors => _vertexAttributes[(int) EAttribute.Colors].Enabled; private const int _faceSize = 3; - private readonly UObject _export; + public readonly UObject Export; public readonly string Path; public readonly string Name; public readonly string Type; @@ -110,8 +107,8 @@ public class Model : IDisposable protected Model(UObject export) { - _export = export; - Path = _export.GetPathName(); + Export = export; + Path = Export.GetPathName(); Name = Path.SubstringAfterLast('/').SubstringBefore('.'); Type = export.ExportType; UvCount = 1; @@ -433,22 +430,6 @@ public class Model : IDisposable if (TwoSided) GL.Enable(EnableCap.CullFace); } - public bool TrySave(out string label, out string savedFilePath) - { - var exportOptions = new ExporterOptions - { - LodFormat = UserSettings.Default.LodExportFormat, - MeshFormat = UserSettings.Default.MeshExportFormat, - MaterialFormat = UserSettings.Default.MaterialExportFormat, - TextureFormat = UserSettings.Default.TextureExportFormat, - SocketFormat = UserSettings.Default.SocketExportFormat, - 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/Options.cs b/FModel/Views/Snooper/Options.cs index 8ee85f1c..441790c5 100644 --- a/FModel/Views/Snooper/Options.cs +++ b/FModel/Views/Snooper/Options.cs @@ -1,7 +1,10 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using CUE4Parse_Conversion; using CUE4Parse_Conversion.Textures; +using CUE4Parse.UE4.Assets.Exports; using CUE4Parse.UE4.Assets.Exports.Texture; using CUE4Parse.UE4.Objects.Core.Misc; using FModel.Settings; @@ -17,7 +20,7 @@ public class Options public FGuid SelectedModel { get; private set; } public int SelectedSection { get; private set; } public int SelectedMorph { get; private set; } - public int SelectedAnimation; + public int SelectedAnimation{ get; set; } public readonly Dictionary Models; public readonly Dictionary Textures; @@ -136,6 +139,8 @@ public class Options public void RemoveAnimations() { Tracker.Reset(); + SelectedAnimation = 0; + foreach (var animation in Animations) { foreach (var guid in animation.AttachedModels) @@ -147,6 +152,10 @@ public class Options } animation.Dispose(); } + foreach (var kvp in Models.ToList().Where(kvp => kvp.Value.IsAnimatedProp)) + { + RemoveModel(kvp.Key); + } Animations.Clear(); } @@ -206,6 +215,22 @@ public class Options Services.ApplicationService.ApplicationView.CUE4Parse.ModelIsWaitingAnimation = value; } + public bool TrySave(UObject export, out string label, out string savedFilePath) + { + var exportOptions = new ExporterOptions + { + LodFormat = UserSettings.Default.LodExportFormat, + MeshFormat = UserSettings.Default.MeshExportFormat, + MaterialFormat = UserSettings.Default.MaterialExportFormat, + TextureFormat = UserSettings.Default.TextureExportFormat, + SocketFormat = UserSettings.Default.SocketExportFormat, + 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 ResetModelsLightsAnimations() { foreach (var model in Models.Values) diff --git a/FModel/Views/Snooper/Renderer.cs b/FModel/Views/Snooper/Renderer.cs index 161120e2..34541888 100644 --- a/FModel/Views/Snooper/Renderer.cs +++ b/FModel/Views/Snooper/Renderer.cs @@ -104,7 +104,7 @@ public class Renderer : IDisposable case UAnimSequence animSequence when animSequence.Skeleton.TryLoad(out USkeleton skeleton): { var animSet = skeleton.ConvertAnims(animSequence); - var animation = new Animation(animSequence.Name, animSet, guid); + var animation = new Animation(animSequence, animSet, guid); maxElapsedTime = animation.TotalElapsedTime; model.Skeleton.Animate(animSet, AnimateWithRotationOnly); Options.AddAnimation(animation); @@ -113,7 +113,7 @@ public class Renderer : IDisposable case UAnimMontage animMontage when animMontage.Skeleton.TryLoad(out USkeleton skeleton): { var animSet = skeleton.ConvertAnims(animMontage); - var animation = new Animation(animMontage.Name, animSet, guid); + var animation = new Animation(animMontage, animSet, guid); maxElapsedTime = animation.TotalElapsedTime; model.Skeleton.Animate(animSet, AnimateWithRotationOnly); Options.AddAnimation(animation); @@ -180,7 +180,7 @@ public class Renderer : IDisposable case UAnimComposite animComposite when animComposite.Skeleton.TryLoad(out USkeleton skeleton): { var animSet = skeleton.ConvertAnims(animComposite); - var animation = new Animation(animComposite.Name, animSet, guid); + var animation = new Animation(animComposite, animSet, guid); maxElapsedTime = animation.TotalElapsedTime; model.Skeleton.Animate(animSet, AnimateWithRotationOnly); Options.AddAnimation(animation); @@ -190,6 +190,7 @@ public class Renderer : IDisposable throw new ArgumentException(); } + Options.Tracker.IsPaused = false; Options.Tracker.SafeSetMaxElapsedTime(maxElapsedTime); Options.AnimateMesh(false); } diff --git a/FModel/Views/Snooper/SnimGui.cs b/FModel/Views/Snooper/SnimGui.cs index b43c4aef..7c3154b3 100644 --- a/FModel/Views/Snooper/SnimGui.cs +++ b/FModel/Views/Snooper/SnimGui.cs @@ -74,7 +74,7 @@ public class SnimGui SectionWindow("Material Inspector", s.Renderer, DrawMaterialInspector, false); AnimationWindow("Timeline", s.Renderer, (icons, tracker, animations) => - tracker.ImGuiTimeline(icons, animations, _outlinerSize, ref s.Renderer.Options.SelectedAnimation, Controller.FontSemiBold)); + tracker.ImGuiTimeline(s, _saver, icons, animations, _outlinerSize, Controller.FontSemiBold)); Window("World", () => DrawWorld(s), false); @@ -102,7 +102,7 @@ public class SnimGui { foreach (var model in s.Renderer.Options.Models.Values) { - b |= model.TrySave(out _, out _); + b |= s.Renderer.Options.TrySave(model.Export, out _, out _); } } @@ -347,7 +347,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio if (ImGui.Selectable("Save")) { s.WindowShouldFreeze(true); - _saver.Value = model.TrySave(out _saver.Label, out _saver.Path); + _saver.Value = s.Renderer.Options.TrySave(model.Export, out _saver.Label, out _saver.Path); s.WindowShouldFreeze(false); } ImGui.BeginDisabled(!model.HasSkeleton); @@ -378,34 +378,34 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio i++; } - Modal("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(); } }); ImGui.PopStyleVar(); + + Modal("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(); + } + }); } private void DrawSockets(Snooper s) @@ -716,7 +716,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio ImGui.PopStyleVar(); } - private void Popup(Action content) + public static void Popup(Action content) { ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(4f)); if (ImGui.BeginPopupContextItem()) diff --git a/FModel/Views/Snooper/Snooper.cs b/FModel/Views/Snooper/Snooper.cs index 5910e886..322e644f 100644 --- a/FModel/Views/Snooper/Snooper.cs +++ b/FModel/Views/Snooper/Snooper.cs @@ -154,6 +154,8 @@ public class Snooper : GameWindow Renderer.CameraOp.Modify(KeyboardState, (float) e.Time); + if (KeyboardState.IsKeyPressed(Keys.Space)) + Renderer.Options.Tracker.IsPaused = !Renderer.Options.Tracker.IsPaused; if (KeyboardState.IsKeyPressed(Keys.Delete)) Renderer.Options.RemoveModel(Renderer.Options.SelectedModel); if (KeyboardState.IsKeyPressed(Keys.H))