diff --git a/FModel/Views/Snooper/Animations/Animation.cs b/FModel/Views/Snooper/Animations/Animation.cs index be5bc26b..f47534cf 100644 --- a/FModel/Views/Snooper/Animations/Animation.cs +++ b/FModel/Views/Snooper/Animations/Animation.cs @@ -15,10 +15,14 @@ public class Animation : IDisposable public readonly float StartTime; // Animation Start Time public readonly float EndTime; // Animation End Time public readonly float TotalElapsedTime; // Animation Max Time + public readonly string TargetSkeleton; public int CurrentSequence; public int FrameInSequence; // Current Sequence's Frame to Display + public bool IsActive; + public bool IsSelected; + public readonly List AttachedModels; public Animation() @@ -30,13 +34,15 @@ public class Animation : IDisposable public Animation(string name, CAnimSet animSet) : this() { Name = name; + TargetSkeleton = animSet.OriginalAnim.Name; + Sequences = new Sequence[animSet.Sequences.Count]; for (int i = 0; i < Sequences.Length; i++) { Sequences[i] = new Sequence(animSet.Sequences[i]); - TotalElapsedTime += animSet.Sequences[i].NumFrames * Sequences[i].TimePerFrame; EndTime = Sequences[i].EndTime; + TotalElapsedTime += animSet.Sequences[i].NumFrames * Sequences[i].TimePerFrame; } if (Sequences.Length > 0) @@ -83,9 +89,16 @@ public class Animation : IDisposable 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); - drawList.AddRectFilled(p1, p2, 0xFF175F17, 5.0f, ImDrawFlags.RoundCornersTop); + ImGui.SetCursorScreenPos(p1); + ImGui.InvisibleButton($"timeline_sequencetracker_{Name}", new Vector2(EndTime * timeRatio.X - t, timeStep.Y - t), ImGuiButtonFlags.MouseButtonLeft); + IsActive = ImGui.IsItemActive(); - drawList.PushClipRect(treeP0, treeP1); + drawList.AddRectFilled(p1, p2, IsSelected ? 0xFF48B048 : 0xFF175F17, 5.0f, ImDrawFlags.RoundCornersTop); + drawList.PushClipRect(p1, p2, true); + drawList.AddText(p1, 0xFF000000, $"{TargetSkeleton} - {CurrentSequence + 1}/{Sequences.Length} - {FrameInSequence}/{Sequences[CurrentSequence].EndFrame}"); + drawList.PopClipRect(); + + drawList.PushClipRect(treeP0 with { Y = p1.Y }, treeP1 with { Y = p2.Y }, true); drawList.AddText(treeP0 with { Y = y + timeStep.Y / 4.0f }, 0xFFFFFFFF, Name); drawList.PopClipRect(); } diff --git a/FModel/Views/Snooper/Animations/Sequence.cs b/FModel/Views/Snooper/Animations/Sequence.cs index ed73fa4e..9a7ea403 100644 --- a/FModel/Views/Snooper/Animations/Sequence.cs +++ b/FModel/Views/Snooper/Animations/Sequence.cs @@ -14,6 +14,7 @@ public class Sequence public readonly float EndTime; public readonly int EndFrame; public readonly int LoopingCount; + public readonly bool IsAdditive; public Sequence(CAnimSequence sequence) { @@ -24,6 +25,7 @@ public class Sequence EndTime = StartTime + Duration; EndFrame = (Duration / TimePerFrame).FloorToInt() - 1; LoopingCount = sequence.LoopingCount; + IsAdditive = sequence.bAdditive; } private readonly float _height = 20.0f; diff --git a/FModel/Views/Snooper/Animations/Skeleton.cs b/FModel/Views/Snooper/Animations/Skeleton.cs index 49b9afd3..11e6ce15 100644 --- a/FModel/Views/Snooper/Animations/Skeleton.cs +++ b/FModel/Views/Snooper/Animations/Skeleton.cs @@ -92,15 +92,9 @@ public class Skeleton : IDisposable { for (int frame = 0; frame < _animatedBonesTransform[s][boneIndices.BoneIndex].Length; frame++) { - // TODO: append the delta transform - // ex: if bone 4, with parent 3, has its first found parent transform at 2, - // we need to append the transform from 4 to 3 so that bone 4 won't be placed where bone 3 should be _animatedBonesTransform[s][boneIndices.BoneIndex][frame] = new Transform { - Relation = _animatedBonesTransform[s][boneIndices.ParentTrackIndex][frame].Matrix, - Rotation = originalTransform.Rotation, - Position = originalTransform.Position, - Scale = originalTransform.Scale + Relation = originalTransform.LocalMatrix * _animatedBonesTransform[s][boneIndices.ParentTrackIndex][frame].Matrix }; } } @@ -219,7 +213,7 @@ public class Skeleton : IDisposable do { var parentBoneIndices = BonesIndicesByLoweredName[loweredParentBoneName]; - if (parentBoneIndices.HasTrack) boneIndices.ParentTrackIndex = parentBoneIndices.BoneIndex; + if (parentBoneIndices.HasTrack || parentBoneIndices.HasParentTrack) boneIndices.ParentTrackIndex = parentBoneIndices.BoneIndex; else loweredParentBoneName = parentBoneIndices.LoweredParentBoneName; } while (!boneIndices.HasParentTrack); } @@ -255,7 +249,7 @@ public class Skeleton : IDisposable _previousSequenceFrame = frameInSequence; _ssbo.Bind(); - for (int boneIndex = 0; boneIndex < BoneCount; boneIndex++) + for (int boneIndex = 0; boneIndex < BoneCount; boneIndex++) // interpolate here _ssbo.Update(boneIndex, _invertedBonesMatrix[boneIndex] * _animatedBonesTransform[_previousAnimationSequence][boneIndex][_previousSequenceFrame].Matrix); _ssbo.Unbind(); } diff --git a/FModel/Views/Snooper/Animations/TimeTracker.cs b/FModel/Views/Snooper/Animations/TimeTracker.cs index e540bc2c..dc3a7b1d 100644 --- a/FModel/Views/Snooper/Animations/TimeTracker.cs +++ b/FModel/Views/Snooper/Animations/TimeTracker.cs @@ -59,24 +59,26 @@ public class TimeTracker : IDisposable private const float _thickness = 2.0f; private const float _timeHeight = 10.0f; private float _timeBarHeight => _timeHeight * 2.0f; + private readonly Vector2 _timeStep = new (50, 25); 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, int activeAnimation, ImFontPtr fontPtr) + public void ImGuiTimeline(Dictionary icons, List animations, Vector2 outliner, ref int activeAnimation, ImFontPtr fontPtr) { var treeP0 = ImGui.GetCursorScreenPos(); var canvasSize = ImGui.GetContentRegionAvail(); - var timelineP1 = new Vector2(treeP0.X + canvasSize.X, treeP0.Y + canvasSize.Y); + var canvasMaxY = MathF.Max(canvasSize.Y, _timeBarHeight + _timeStep.Y * animations.Count); + ImGui.BeginChild("timeline_child", canvasSize with { Y = canvasMaxY }); + var timelineP1 = new Vector2(treeP0.X + canvasSize.X, treeP0.Y + canvasMaxY); var treeP1 = timelineP1 with { X = treeP0.X + outliner.X }; var timelineP0 = treeP0 with { X = treeP1.X + _thickness }; var timelineSize = timelineP1 - timelineP0; var timeRatio = timelineSize / MaxElapsedTime; - var timeStep = timeRatio * MaxElapsedTime / timelineSize * 50.0f; - timeStep.Y /= 2.0f; var drawList = ImGui.GetWindowDrawList(); + drawList.PushClipRect(treeP0, timelineP1, true); drawList.AddRectFilled(treeP0, treeP1, 0xFF1F1C1C); drawList.AddRectFilled(timelineP0, timelineP1 with { Y = timelineP0.Y + _timeBarHeight }, 0xFF141414); drawList.AddRectFilled(timelineP0 with { Y = timelineP0.Y + _timeBarHeight }, timelineP1, 0xFF242424); @@ -97,14 +99,14 @@ public class TimeTracker : IDisposable switch (i) { case 0: - SafeSetElapsedTime(ElapsedTime + timeStep.X / timeRatio.X); + SafeSetElapsedTime(ElapsedTime + _timeStep.X / timeRatio.X); break; case 1: IsPaused = !IsPaused; _icons[1] = IsPaused ? "tl_play" : "tl_pause"; break; case 2: - SafeSetElapsedTime(ElapsedTime - timeStep.X / timeRatio.X); + SafeSetElapsedTime(ElapsedTime - _timeStep.X / timeRatio.X); break; } } @@ -124,7 +126,7 @@ public class TimeTracker : IDisposable } { // draw time + time grid - for (float x = 0; x < timelineSize.X; x += timeStep.X) + for (float x = 0; x < timelineSize.X; x += _timeStep.X) { var cursor = timelineP0.X + x; drawList.AddLine(new Vector2(cursor, timelineP0.Y + _timeHeight + 2.5f), new Vector2(cursor, timelineP0.Y + _timeBarHeight), 0xA0FFFFFF); @@ -132,7 +134,7 @@ public class TimeTracker : IDisposable drawList.AddText(fontPtr, 14, new Vector2(cursor + 4, timelineP0.Y + 7.5f), 0x50FFFFFF, $"{x / timeRatio.X:F1}s"); } - for (float y = _timeBarHeight; y < timelineSize.Y; y += timeStep.Y) + for (float y = _timeBarHeight; y < timelineSize.Y; y += _timeStep.Y) { drawList.AddLine(timelineP0 with { Y = timelineP0.Y + y }, timelineP1 with { Y = timelineP0.Y + y }, 0x28C8C8C8); } @@ -140,12 +142,20 @@ 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); - DrawSeparator(drawList, timelineP0, y + timeStep.Y, animations[i].EndTime * timeRatio.X, ETrackerType.End); + 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; + + DrawSeparator(drawList, timelineP0, y + _timeStep.Y, animations[i].EndTime * timeRatio.X, ETrackerType.End); } DrawSeparator(drawList, timelineP0, timelineP1.Y, ElapsedTime * timeRatio.X, ETrackerType.Frame); + + drawList.PopClipRect(); + ImGui.EndChild(); } private void DrawSeparator(ImDrawListPtr drawList, Vector2 origin, float y, float time, ETrackerType separatorType) diff --git a/FModel/Views/Snooper/Models/Cube.cs b/FModel/Views/Snooper/Models/Cube.cs index 21f5f6f7..3681cf08 100644 --- a/FModel/Views/Snooper/Models/Cube.cs +++ b/FModel/Views/Snooper/Models/Cube.cs @@ -1,13 +1,12 @@ using CUE4Parse_Conversion.Meshes.PSK; using CUE4Parse.UE4.Assets.Exports.Material; -using CUE4Parse.UE4.Objects.Core.Misc; using FModel.Views.Snooper.Shading; namespace FModel.Views.Snooper.Models; public class Cube : Model { - public Cube(CStaticMesh mesh, FGuid guid, UMaterialInterface unrealMaterial) : base(unrealMaterial, guid) + public Cube(CStaticMesh mesh, UMaterialInterface unrealMaterial) : base(unrealMaterial) { var lod = mesh.LODs[0]; diff --git a/FModel/Views/Snooper/Models/Model.cs b/FModel/Views/Snooper/Models/Model.cs index 907e6bab..38aa1366 100644 --- a/FModel/Views/Snooper/Models/Model.cs +++ b/FModel/Views/Snooper/Models/Model.cs @@ -13,7 +13,6 @@ using CUE4Parse.UE4.Assets.Exports.Material; using CUE4Parse.UE4.Assets.Exports.SkeletalMesh; using CUE4Parse.UE4.Assets.Exports.StaticMesh; using CUE4Parse.UE4.Objects.Core.Math; -using CUE4Parse.UE4.Objects.Core.Misc; using FModel.Extensions; using FModel.Settings; using FModel.Views.Snooper.Animations; @@ -70,7 +69,6 @@ public class Model : IDisposable private const int _faceSize = 3; private readonly UObject _export; - public readonly FGuid Guid; public readonly string Path; public readonly string Name; public readonly string Type; @@ -110,10 +108,9 @@ public class Model : IDisposable public int SelectedInstance; public float MorphTime; - protected Model(UObject export, FGuid guid) + protected Model(UObject export) { _export = export; - Guid = guid; Path = _export.GetPathName(); Name = Path.SubstringAfterLast('/').SubstringBefore('.'); Type = export.ExportType; @@ -124,8 +121,8 @@ public class Model : IDisposable Transforms = new List(); } - public Model(UStaticMesh export, FGuid guid, CStaticMesh staticMesh) : this(export, guid, staticMesh, Transform.Identity) {} - public Model(UStaticMesh export, FGuid guid, CStaticMesh staticMesh, Transform transform) : this(export, guid, export.Materials, staticMesh.LODs, transform) + public Model(UStaticMesh export, CStaticMesh staticMesh) : this(export, staticMesh, Transform.Identity) {} + public Model(UStaticMesh export, CStaticMesh staticMesh, Transform transform) : this(export, export.Materials, staticMesh.LODs, transform) { Box = staticMesh.BoundingBox * Constants.SCALE_DOWN_RATIO; @@ -136,8 +133,8 @@ public class Model : IDisposable } } - public Model(USkeletalMesh export, FGuid guid, CSkeletalMesh skeletalMesh) : this(export, guid, skeletalMesh, Transform.Identity) {} - public Model(USkeletalMesh export, FGuid guid, CSkeletalMesh skeletalMesh, Transform transform) : this(export, guid, export.Materials, skeletalMesh.LODs, transform) + public Model(USkeletalMesh export, CSkeletalMesh skeletalMesh) : this(export, skeletalMesh, Transform.Identity) {} + public Model(USkeletalMesh export, CSkeletalMesh skeletalMesh, Transform transform) : this(export, export.Materials, skeletalMesh.LODs, transform) { Box = skeletalMesh.BoundingBox * Constants.SCALE_DOWN_RATIO; Skeleton = new Skeleton(export.ReferenceSkeleton); @@ -163,11 +160,11 @@ public class Model : IDisposable } } - private Model(UObject export, FGuid guid, IReadOnlyList materials, IReadOnlyList lods, Transform transform = null) - : this(export, guid, materials, lods[_LOD_INDEX], lods[_LOD_INDEX].Verts, lods.Count, transform) {} - private Model(UObject export, FGuid guid, IReadOnlyList materials, IReadOnlyList lods, Transform transform = null) - : this(export, guid, materials, lods[_LOD_INDEX], lods[_LOD_INDEX].Verts, lods.Count, transform) {} - private Model(UObject export, FGuid guid, IReadOnlyList materials, CBaseMeshLod lod, IReadOnlyList vertices, int numLods, Transform transform = null) : this(export, guid) + private Model(UObject export, IReadOnlyList materials, IReadOnlyList lods, Transform transform = null) + : this(export, materials, lods[_LOD_INDEX], lods[_LOD_INDEX].Verts, lods.Count, transform) {} + private Model(UObject export, IReadOnlyList materials, IReadOnlyList lods, Transform transform = null) + : this(export, materials, lods[_LOD_INDEX], lods[_LOD_INDEX].Verts, lods.Count, transform) {} + private Model(UObject export, IReadOnlyList materials, CBaseMeshLod lod, IReadOnlyList vertices, int numLods, Transform transform = null) : this(export) { var hasCustomUvs = lod.ExtraUV.IsValueCreated; UvCount = hasCustomUvs ? Math.Max(lod.NumTexCoords, numLods) : lod.NumTexCoords; @@ -293,9 +290,9 @@ public class Model : IDisposable _morphVbo.Unbind(); } - public void AttachModel(Model attachedTo, Socket socket) + public void AttachModel(Model attachedTo, Socket socket, SocketAttachementInfo info) { - socket.AttachedModels.Add(new SocketAttachementInfo { Guid = Guid, Instance = SelectedInstance }); + socket.AttachedModels.Add(info); _attachedTo = $"'{socket.Name}' from '{attachedTo.Name}'{(!socket.BoneName.IsNone ? $" at '{socket.BoneName}'" : "")}"; attachedTo._attachedFor.Add($"'{Name}'"); @@ -305,9 +302,9 @@ public class Model : IDisposable Transforms[SelectedInstance].Scale = FVector.OneVector; } - public void DetachModel(Model attachedTo, Socket socket) + public void DetachModel(Model attachedTo, Socket socket, SocketAttachementInfo info) { - socket.AttachedModels.Remove(new SocketAttachementInfo { Guid = Guid, Instance = SelectedInstance }); + socket.AttachedModels.Remove(info); SafeDetachModel(attachedTo); } diff --git a/FModel/Views/Snooper/Options.cs b/FModel/Views/Snooper/Options.cs index 844e3cc2..8ee85f1c 100644 --- a/FModel/Views/Snooper/Options.cs +++ b/FModel/Views/Snooper/Options.cs @@ -17,7 +17,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 { get; private set; } + public int SelectedAnimation; public readonly Dictionary Models; public readonly Dictionary Textures; diff --git a/FModel/Views/Snooper/Renderer.cs b/FModel/Views/Snooper/Renderer.cs index e07ec006..161120e2 100644 --- a/FModel/Views/Snooper/Renderer.cs +++ b/FModel/Views/Snooper/Renderer.cs @@ -92,15 +92,11 @@ public class Renderer : IDisposable Options.SwapMaterial(false); } - public void Animate(UObject anim) + public void Animate(UObject anim) => Animate(anim, Options.SelectedModel); + private void Animate(UObject anim, FGuid guid) { - if (!Options.TryGetModel(out var model)) + if (!Options.TryGetModel(guid, out var model) || !model.HasSkeleton) return; - Animate(anim, model); - } - private void Animate(UObject anim, Model model) - { - if (!model.HasSkeleton) return; float maxElapsedTime; switch (anim) @@ -108,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, model.Guid); + var animation = new Animation(animSequence.Name, animSet, guid); maxElapsedTime = animation.TotalElapsedTime; model.Skeleton.Animate(animSet, AnimateWithRotationOnly); Options.AddAnimation(animation); @@ -117,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, model.Guid); + var animation = new Animation(animMontage.Name, animSet, guid); maxElapsedTime = animation.TotalElapsedTime; model.Skeleton.Animate(animSet, AnimateWithRotationOnly); Options.AddAnimation(animation); @@ -136,7 +132,6 @@ public class Renderer : IDisposable t.Scale = offset.Scale3D; } - FGuid guid; switch (export) { case UStaticMesh st: @@ -145,14 +140,14 @@ public class Renderer : IDisposable if (Options.TryGetModel(guid, out var instancedModel)) instancedModel.AddInstance(t); else if (st.TryConvert(out var mesh)) - Options.Models[guid] = new Model(st, guid, mesh, t); + Options.Models[guid] = new Model(st, mesh, t); break; } case USkeletalMesh sk: { - guid = new FGuid((uint) sk.GetFullName().GetHashCode()); + guid = Guid.NewGuid(); if (!Options.Models.ContainsKey(guid) && sk.TryConvert(out var mesh)) - Options.Models[guid] = new Model(sk, guid, mesh, t); + Options.Models[guid] = new Model(sk, mesh, t); break; } default: @@ -164,7 +159,7 @@ public class Renderer : IDisposable addedModel.IsAnimatedProp = true; if (notifyClass.TryGetValue(out UObject skeletalMeshPropAnimation, "SkeletalMeshPropAnimation", "Animation")) - Animate(skeletalMeshPropAnimation, addedModel); + Animate(skeletalMeshPropAnimation, guid); if (notifyClass.TryGetValue(out FName socketName, "SocketName")) { t = Transform.Identity; @@ -177,7 +172,7 @@ public class Renderer : IDisposable var s = new Socket($"TL_{addedModel.Name}", socketName, t, true); model.Sockets.Add(s); - addedModel.AttachModel(model, s); + addedModel.AttachModel(model, s, new SocketAttachementInfo { Guid = guid, Instance = addedModel.SelectedInstance }); } } break; @@ -185,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, model.Guid); + var animation = new Animation(animComposite.Name, animSet, guid); maxElapsedTime = animation.TotalElapsedTime; model.Skeleton.Animate(animSet, AnimateWithRotationOnly); Options.AddAnimation(animation); @@ -280,7 +275,7 @@ public class Renderer : IDisposable if (!original.TryConvert(out var mesh)) return; - Options.Models[guid] = new Model(original, guid, mesh); + Options.Models[guid] = new Model(original, mesh); Options.SelectModel(guid); SetupCamera(Options.Models[guid].Box); } @@ -290,7 +285,7 @@ public class Renderer : IDisposable var guid = new FGuid((uint) original.GetFullName().GetHashCode()); if (Options.Models.ContainsKey(guid) || !original.TryConvert(out var mesh)) return; - Options.Models[guid] = new Model(original, guid, mesh); + Options.Models[guid] = new Model(original, mesh); Options.SelectModel(guid); SetupCamera(Options.Models[guid].Box); } @@ -311,7 +306,7 @@ public class Renderer : IDisposable if (!editorCube.TryConvert(out var mesh)) return; - Options.Models[guid] = new Cube(mesh, guid, original); + Options.Models[guid] = new Cube(mesh, original); Options.SelectModel(guid); SetupCamera(Options.Models[guid].Box); } @@ -403,7 +398,7 @@ public class Renderer : IDisposable } else if (m.TryConvert(out var mesh)) { - model = new Model(m, guid, mesh, t); + model = new Model(m, mesh, t); model.TwoSided = actor.GetOrDefault("bMirrored", staticMeshComp.GetOrDefault("bDisallowMeshPaintPerInstance", model.TwoSided)); if (actor.TryGetValue(out FPackageIndex baseMaterial, "BaseMaterial") && diff --git a/FModel/Views/Snooper/Shading/Material.cs b/FModel/Views/Snooper/Shading/Material.cs index 4882dd85..0c6bd4ea 100644 --- a/FModel/Views/Snooper/Shading/Material.cs +++ b/FModel/Views/Snooper/Shading/Material.cs @@ -358,7 +358,7 @@ public class Material : IDisposable var canvasSize = ImGui.GetContentRegionAvail(); if (canvasSize.X < 50.0f) canvasSize.X = 50.0f; if (canvasSize.Y < 50.0f) canvasSize.Y = 50.0f; - var canvasP1 = new Vector2(canvasP0.X + canvasSize.X, canvasP0.Y + canvasSize.Y); + var canvasP1 = canvasP0 + canvasSize; var origin = new Vector2(canvasP0.X + _scrolling.X, canvasP0.Y + _scrolling.Y); var absoluteMiddle = canvasSize / 2.0f; diff --git a/FModel/Views/Snooper/SnimGui.cs b/FModel/Views/Snooper/SnimGui.cs index 5ba99601..b43c4aef 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, s.Renderer.Options.SelectedAnimation, Controller.FontSemiBold)); + tracker.ImGuiTimeline(icons, animations, _outlinerSize, ref s.Renderer.Options.SelectedAnimation, Controller.FontSemiBold)); Window("World", () => DrawWorld(s), false); @@ -412,6 +412,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio { MeshWindow("Sockets", s.Renderer, (icons, selectedModel) => { + var info = new SocketAttachementInfo { Guid = s.Renderer.Options.SelectedModel, Instance = selectedModel.SelectedInstance }; foreach (var model in s.Renderer.Options.Models.Values) { if (!model.HasSockets || model.IsSelected) continue; @@ -420,16 +421,16 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio var i = 0; foreach (var socket in model.Sockets) { - var isAttached = socket.AttachedModels.Contains(new SocketAttachementInfo { Guid = selectedModel.Guid, Instance = selectedModel.SelectedInstance }); + var isAttached = socket.AttachedModels.Contains(info); ImGui.PushID(i); ImGui.BeginDisabled(selectedModel.IsAttached && !isAttached); switch (isAttached) { case false when ImGui.Button($"Attach to '{socket.Name}'"): - selectedModel.AttachModel(model, socket); + selectedModel.AttachModel(model, socket, info); break; case true when ImGui.Button($"Detach from '{socket.Name}'"): - selectedModel.DetachModel(model, socket); + selectedModel.DetachModel(model, socket, info); break; } ImGui.EndDisabled();