diff --git a/FModel/Views/Snooper/Animations/Bone.cs b/FModel/Views/Snooper/Animations/Bone.cs index 5fd846df..337c5bbc 100644 --- a/FModel/Views/Snooper/Animations/Bone.cs +++ b/FModel/Views/Snooper/Animations/Bone.cs @@ -7,21 +7,27 @@ public class Bone public readonly int Index; public readonly int ParentIndex; public readonly Transform Rest; + public readonly bool IsVirtual; + public string LoweredParentName; + public List LoweredChildNames; public int SkeletonIndex = -1; public readonly List AnimatedBySequences; - public Bone(int i, int p, Transform t) + public Bone(int i, int p, Transform t, bool isVirtual = false) { Index = i; ParentIndex = p; Rest = t; + IsVirtual = isVirtual; + LoweredChildNames = new List(); AnimatedBySequences = new List(); } public bool IsRoot => Index == 0 && ParentIndex == -1 && string.IsNullOrEmpty(LoweredParentName); + public bool IsDaron => LoweredChildNames.Count > 0; public bool IsMapped => SkeletonIndex > -1; public bool IsAnimated => AnimatedBySequences.Count > 0; public bool IsNative => Index == SkeletonIndex; diff --git a/FModel/Views/Snooper/Animations/Skeleton.cs b/FModel/Views/Snooper/Animations/Skeleton.cs index 47bd8f10..d039b802 100644 --- a/FModel/Views/Snooper/Animations/Skeleton.cs +++ b/FModel/Views/Snooper/Animations/Skeleton.cs @@ -6,6 +6,7 @@ using CUE4Parse_Conversion.Animations.PSA; using CUE4Parse.UE4.Assets.Exports.Animation; using FModel.Views.Snooper.Buffers; using FModel.Views.Snooper.Shading; +using ImGuiNET; using OpenTK.Graphics.OpenGL4; using Serilog; @@ -16,13 +17,18 @@ public class Skeleton : IDisposable private int _handle; private BufferObject _rest; private BufferObject _ssbo; + private Matrix4x4[] _boneMatriceAtFrame; public string Name; - public bool IsAnimated { get; private set; } + public readonly string RootBoneName; + public readonly Dictionary BonesByLoweredName; public readonly int BoneCount; - public readonly Dictionary BonesByLoweredName; - private Matrix4x4[] _boneMatriceAtFrame; + public int AdditionalBoneCount; + public int TotalBoneCount => BoneCount + AdditionalBoneCount; + + public bool IsAnimated { get; private set; } + public string SelectedBone; public Skeleton() { @@ -35,23 +41,75 @@ public class Skeleton : IDisposable for (int boneIndex = 0; boneIndex < BoneCount; boneIndex++) { var info = referenceSkeleton.FinalRefBoneInfo[boneIndex]; - var boneTransform = new Transform + var boneName = info.Name.Text.ToLower(); + var bone = new Bone(boneIndex, info.ParentIndex, new Transform { Rotation = referenceSkeleton.FinalRefBonePose[boneIndex].Rotation, Position = referenceSkeleton.FinalRefBonePose[boneIndex].Translation * Constants.SCALE_DOWN_RATIO, Scale = referenceSkeleton.FinalRefBonePose[boneIndex].Scale3D - }; + }); - var bone = new Bone(boneIndex, info.ParentIndex, boneTransform); if (!bone.IsRoot) { - bone.LoweredParentName = - referenceSkeleton.FinalRefBoneInfo[bone.ParentIndex].Name.Text.ToLower(); - bone.Rest.Relation = BonesByLoweredName[bone.LoweredParentName].Rest.Matrix; + bone.LoweredParentName = referenceSkeleton.FinalRefBoneInfo[bone.ParentIndex].Name.Text.ToLower(); + var parentBone = BonesByLoweredName[bone.LoweredParentName]; + + bone.Rest.Relation = parentBone.Rest.Matrix; + parentBone.LoweredChildNames.Add(boneName); } - BonesByLoweredName[info.Name.Text.ToLower()] = bone; + if (boneIndex == 0) RootBoneName = boneName; + BonesByLoweredName[boneName] = bone; } + _boneMatriceAtFrame = new Matrix4x4[BoneCount]; + } + + public void Merge(FReferenceSkeleton referenceSkeleton) + { + for (int boneIndex = 0; boneIndex < referenceSkeleton.FinalRefBoneInfo.Length; boneIndex++) + { + var info = referenceSkeleton.FinalRefBoneInfo[boneIndex]; + var boneName = info.Name.Text.ToLower(); + + if (!BonesByLoweredName.TryGetValue(boneName, out var bone)) + { + bone = new Bone(BoneCount + AdditionalBoneCount, info.ParentIndex, new Transform + { + Rotation = referenceSkeleton.FinalRefBonePose[boneIndex].Rotation, + Position = referenceSkeleton.FinalRefBonePose[boneIndex].Translation * Constants.SCALE_DOWN_RATIO, + Scale = referenceSkeleton.FinalRefBonePose[boneIndex].Scale3D + }, true); + + if (!bone.IsRoot) + { + bone.LoweredParentName = referenceSkeleton.FinalRefBoneInfo[bone.ParentIndex].Name.Text.ToLower(); + var parentBone = BonesByLoweredName[bone.LoweredParentName]; + + bone.Rest.Relation = parentBone.Rest.Matrix; + parentBone.LoweredChildNames.Add(boneName); + } + + BonesByLoweredName[boneName] = bone; + AdditionalBoneCount++; + } + } + _boneMatriceAtFrame = new Matrix4x4[BoneCount + AdditionalBoneCount]; + } + + public void Setup() + { + _handle = GL.CreateProgram(); + + _rest = new BufferObject(BoneCount, BufferTarget.ShaderStorageBuffer); + foreach (var bone in BonesByLoweredName.Values) + { + if (bone.IsVirtual) break; + _rest.Update(bone.Index, bone.Rest.Matrix); + } + _rest.Unbind(); + + _ssbo = new BufferObject(TotalBoneCount, BufferTarget.ShaderStorageBuffer); + _ssbo.UpdateRange(Matrix4x4.Identity); } public void Animate(CAnimSet animation) @@ -101,28 +159,7 @@ public class Skeleton : IDisposable if (!full) return; IsAnimated = false; - _ssbo.UpdateRange(BoneCount, Matrix4x4.Identity); - } - - public void Setup() - { - _handle = GL.CreateProgram(); - - _rest = new BufferObject(BoneCount, BufferTarget.ShaderStorageBuffer); - foreach (var bone in BonesByLoweredName.Values) - { - _rest.Update(bone.Index, bone.Rest.Matrix); - } - _rest.Unbind(); - - _ssbo = new BufferObject(BoneCount, BufferTarget.ShaderStorageBuffer); - _ssbo.UpdateRange(BoneCount, Matrix4x4.Identity); - - _boneMatriceAtFrame = new Matrix4x4[BoneCount]; - for (int boneIndex = 0; boneIndex < _boneMatriceAtFrame.Length; boneIndex++) - { - _boneMatriceAtFrame[boneIndex] = Matrix4x4.Identity; - } + _ssbo.UpdateRange(Matrix4x4.Identity); } public void UpdateAnimationMatrices(Animation animation, bool rotationOnly) @@ -191,14 +228,7 @@ public class Skeleton : IDisposable return (s, f); } - public Matrix4x4 GetBoneMatrix(Bone bone) - { - _ssbo.Bind(); - var anim = _ssbo.Get(bone.Index); - _ssbo.Unbind(); - - return IsAnimated ? anim : bone.Rest.Matrix; - } + public Matrix4x4 GetBoneMatrix(Bone bone) => IsAnimated ? _boneMatriceAtFrame[bone.Index] : bone.Rest.Matrix; public void Render(Shader shader) { @@ -208,6 +238,33 @@ public class Skeleton : IDisposable _rest.BindBufferBase(2); } + public void ImGuiBoneHierarchy() + { + DrawBoneTree(RootBoneName, BonesByLoweredName[RootBoneName]); + } + + private void DrawBoneTree(string boneName, Bone bone) + { + var flags = ImGuiTreeNodeFlags.OpenOnArrow | ImGuiTreeNodeFlags.OpenOnDoubleClick | ImGuiTreeNodeFlags.SpanAvailWidth; + if (boneName == SelectedBone) flags |= ImGuiTreeNodeFlags.Selected; + if (bone.IsVirtual) flags |= ImGuiTreeNodeFlags.Leaf; + else if (!bone.IsDaron) flags |= ImGuiTreeNodeFlags.Bullet; + + ImGui.SetNextItemOpen(bone.LoweredChildNames.Count <= 1, ImGuiCond.Appearing); + var open = ImGui.TreeNodeEx(boneName, flags); + if (ImGui.IsItemClicked() && !ImGui.IsItemToggledOpen()) + SelectedBone = boneName; + + if (open) + { + foreach (var name in bone.LoweredChildNames) + { + DrawBoneTree(name, BonesByLoweredName[name]); + } + ImGui.TreePop(); + } + } + public void Dispose() { BonesByLoweredName.Clear(); diff --git a/FModel/Views/Snooper/Buffers/BufferObject.cs b/FModel/Views/Snooper/Buffers/BufferObject.cs index 028b95e0..dbda6ee5 100644 --- a/FModel/Views/Snooper/Buffers/BufferObject.cs +++ b/FModel/Views/Snooper/Buffers/BufferObject.cs @@ -9,6 +9,8 @@ public class BufferObject : IDisposable where TDataType : unmanaged private readonly int _sizeOf; private readonly BufferTarget _bufferTarget; + public readonly int Size; + private unsafe BufferObject(BufferTarget bufferTarget) { _bufferTarget = bufferTarget; @@ -20,14 +22,17 @@ public class BufferObject : IDisposable where TDataType : unmanaged public BufferObject(TDataType[] data, BufferTarget bufferTarget) : this(bufferTarget) { - GL.BufferData(bufferTarget, data.Length * _sizeOf, data, BufferUsageHint.StaticDraw); + Size = data.Length; + GL.BufferData(bufferTarget, Size * _sizeOf, data, BufferUsageHint.StaticDraw); } public BufferObject(int length, BufferTarget bufferTarget) : this(bufferTarget) { - GL.BufferData(bufferTarget, length * _sizeOf, IntPtr.Zero, BufferUsageHint.DynamicDraw); + Size = length; + GL.BufferData(bufferTarget, Size * _sizeOf, IntPtr.Zero, BufferUsageHint.DynamicDraw); } + public void UpdateRange(TDataType data) => UpdateRange(Size, data); public void UpdateRange(int count, TDataType data) { Bind(); diff --git a/FModel/Views/Snooper/Models/Model.cs b/FModel/Views/Snooper/Models/Model.cs index 05739dd0..b818ff18 100644 --- a/FModel/Views/Snooper/Models/Model.cs +++ b/FModel/Views/Snooper/Models/Model.cs @@ -141,6 +141,7 @@ public class Model : IDisposable if (HasSkeleton && export.Skeleton.TryLoad(out USkeleton skeleton)) { Skeleton.Name = skeleton.Name; + // Skeleton.Merge(skeleton.ReferenceSkeleton); sockets.AddRange(skeleton.Sockets); } @@ -255,6 +256,8 @@ public class Model : IDisposable var worldMatrix = UpdateMatrices(); foreach (var socket in Sockets) { + if (!socket.IsDaron) continue; + var boneMatrix = Matrix4x4.Identity; if (HasSkeleton && Skeleton.BonesByLoweredName.TryGetValue(socket.BoneName.Text.ToLower(), out var bone)) boneMatrix = Skeleton.GetBoneMatrix(bone); @@ -415,6 +418,7 @@ public class Model : IDisposable GL.DrawElementsInstanced(PrimitiveType.Triangles, section.FacesCount, DrawElementsType.UnsignedInt, section.FirstFaceIndexPtr, TransformsCount); } + GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill); _vao.Unbind(); if (IsSelected) diff --git a/FModel/Views/Snooper/Models/Socket.cs b/FModel/Views/Snooper/Models/Socket.cs index 8c37b422..8199b16b 100644 --- a/FModel/Views/Snooper/Models/Socket.cs +++ b/FModel/Views/Snooper/Models/Socket.cs @@ -21,6 +21,7 @@ public class Socket : IDisposable public readonly bool IsVirtual; public readonly List AttachedModels; + public bool IsDaron => AttachedModels.Count > 0; private Socket() { diff --git a/FModel/Views/Snooper/Renderer.cs b/FModel/Views/Snooper/Renderer.cs index 0b2ee808..156aff71 100644 --- a/FModel/Views/Snooper/Renderer.cs +++ b/FModel/Views/Snooper/Renderer.cs @@ -179,7 +179,7 @@ public class Renderer : IDisposable if (notifyClass.TryGetValue(out FVector scale, "Scale")) t.Scale = scale; - var s = new Socket($"TL_{addedModel.Name}", socketName, t, true); + var s = new Socket($"ANIM_{addedModel.Name}", socketName, t, true); model.Sockets.Add(s); addedModel.AttachModel(model, s, new SocketAttachementInfo { Guid = guid, Instance = addedModel.SelectedInstance }); } diff --git a/FModel/Views/Snooper/SnimGui.cs b/FModel/Views/Snooper/SnimGui.cs index ad40674d..954fef74 100644 --- a/FModel/Views/Snooper/SnimGui.cs +++ b/FModel/Views/Snooper/SnimGui.cs @@ -67,6 +67,7 @@ public class SnimGui private Vector2 _outlinerSize; private bool _ti_open; + private bool _bh_open; private bool _viewportFocus; private readonly Vector4 _accentColor = new (0.125f, 0.42f, 0.831f, 1.0f); @@ -106,6 +107,7 @@ public class SnimGui DrawModals(s); if (_ti_open) DrawTextureInspector(s); + if (_bh_open) DrawBoneHierarchy(s); Controller.Render(); } @@ -210,52 +212,43 @@ public class SnimGui ImGui.EndTable(); } - ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); - if (ImGui.CollapsingHeader("Editor")) + ImGui.SeparatorText("Editor"); + if (ImGui.BeginTable("world_editor", 2)) { - if (ImGui.BeginTable("world_editor", 2)) - { - Layout("Skybox");ImGui.PushID(1); - ImGui.Checkbox("", ref s.Renderer.ShowSkybox); - ImGui.PopID();Layout("Grid");ImGui.PushID(2); - ImGui.Checkbox("", ref s.Renderer.ShowGrid); - ImGui.PopID();Layout("Lights");ImGui.PushID(3); - ImGui.Checkbox("", ref s.Renderer.ShowLights); - ImGui.PopID();Layout("Animate With Rotation Only");ImGui.PushID(4); - ImGui.Checkbox("", ref s.Renderer.AnimateWithRotationOnly); - ImGui.PopID();Layout("Vertex Colors");ImGui.PushID(5); - var c = (int) s.Renderer.Color; - ImGui.Combo("vertex_colors", ref c, - "Default\0Sections\0Colors\0Normals\0Texture Coordinates\0"); - s.Renderer.Color = (VertexColor) c; - ImGui.PopID(); + Layout("Skybox");ImGui.PushID(1); + ImGui.Checkbox("", ref s.Renderer.ShowSkybox); + ImGui.PopID();Layout("Grid");ImGui.PushID(2); + ImGui.Checkbox("", ref s.Renderer.ShowGrid); + ImGui.PopID();Layout("Lights");ImGui.PushID(3); + ImGui.Checkbox("", ref s.Renderer.ShowLights); + ImGui.PopID();Layout("Animate With Rotation Only");ImGui.PushID(4); + ImGui.Checkbox("", ref s.Renderer.AnimateWithRotationOnly); + ImGui.PopID();Layout("Vertex Colors");ImGui.PushID(5); + var c = (int) s.Renderer.Color; + ImGui.Combo("vertex_colors", ref c, + "Default\0Sections\0Colors\0Normals\0Texture Coordinates\0"); + s.Renderer.Color = (VertexColor) c; + ImGui.PopID(); + ImGui.EndTable(); + } + + ImGui.SeparatorText("Camera"); + s.Renderer.CameraOp.ImGuiCamera(); + + ImGui.SeparatorText("Lights"); + for (int i = 0; i < s.Renderer.Options.Lights.Count; i++) + { + var light = s.Renderer.Options.Lights[i]; + var id = s.Renderer.Options.TryGetModel(light.Model, out var lightModel) ? lightModel.Name : "None"; + + id += $"##{i}"; + if (ImGui.TreeNode(id) && ImGui.BeginTable(id, 2)) + { + s.Renderer.Options.SelectModel(light.Model); + light.ImGuiLight(); ImGui.EndTable(); - } - } - - ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); - if (ImGui.CollapsingHeader("Camera")) - { - s.Renderer.CameraOp.ImGuiCamera(); - } - - ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); - if (ImGui.CollapsingHeader("Lights")) - { - for (int i = 0; i < s.Renderer.Options.Lights.Count; i++) - { - var light = s.Renderer.Options.Lights[i]; - var id = s.Renderer.Options.TryGetModel(light.Model, out var lightModel) ? lightModel.Name : "None"; - - id += $"##{i}"; - if (ImGui.TreeNode(id) && ImGui.BeginTable(id, 2)) - { - s.Renderer.Options.SelectModel(light.Model); - light.ImGuiLight(); - ImGui.EndTable(); - ImGui.TreePop(); - } + ImGui.TreePop(); } } } @@ -461,6 +454,11 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio _swapper.Value = true; } } + if (ImGui.MenuItem("Bone Hierarchy", model.HasSkeleton)) + { + _bh_open = true; + ImGui.SetWindowFocus("Bone Hierarchy"); + } if (ImGui.MenuItem("Teleport To")) { var instancePos = model.Transforms[model.SelectedInstance].Matrix.Translation; @@ -665,56 +663,50 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio ImGui.SameLine(); ImGui.AlignTextToFramePadding(); ImGui.Text(material.Name); ImGui.Spacing(); - ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); - if (ImGui.CollapsingHeader("Parameters")) - { - material.ImGuiParameters(); - } + ImGui.SeparatorText("Parameters"); + material.ImGuiParameters(); - ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); - if (ImGui.CollapsingHeader("Textures") && material.ImGuiTextures(icons, model)) + ImGui.SeparatorText("Textures"); + if (material.ImGuiTextures(icons, model)) { _ti_open = true; ImGui.SetWindowFocus("Texture Inspector"); } - ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); - if (ImGui.CollapsingHeader("Properties")) + ImGui.SeparatorText("Properties"); + NoFramePaddingOnY(() => { - NoFramePaddingOnY(() => + ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); + if (ImGui.TreeNode("Base")) { - ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); - if (ImGui.TreeNode("Base")) - { - material.ImGuiBaseProperties("base"); - ImGui.TreePop(); - } + material.ImGuiBaseProperties("base"); + ImGui.TreePop(); + } - ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); - if (ImGui.TreeNode("Scalars")) - { - material.ImGuiDictionaries("scalars", material.Parameters.Scalars, true); - ImGui.TreePop(); - } - ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); - if (ImGui.TreeNode("Switches")) - { - material.ImGuiDictionaries("switches", material.Parameters.Switches, true); - ImGui.TreePop(); - } - ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); - if (ImGui.TreeNode("Colors")) - { - material.ImGuiColors(material.Parameters.Colors); - ImGui.TreePop(); - } - if (ImGui.TreeNode("All Textures")) - { - material.ImGuiDictionaries("textures", material.Parameters.Textures); - ImGui.TreePop(); - } - }); - } + ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); + if (ImGui.TreeNode("Scalars")) + { + material.ImGuiDictionaries("scalars", material.Parameters.Scalars, true); + ImGui.TreePop(); + } + ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); + if (ImGui.TreeNode("Switches")) + { + material.ImGuiDictionaries("switches", material.Parameters.Switches, true); + ImGui.TreePop(); + } + ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); + if (ImGui.TreeNode("Colors")) + { + material.ImGuiColors(material.Parameters.Colors); + ImGui.TreePop(); + } + if (ImGui.TreeNode("All Textures")) + { + material.ImGuiDictionaries("textures", material.Parameters.Textures); + ImGui.TreePop(); + } + }); } private void DrawTextureInspector(Snooper s) @@ -728,6 +720,15 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio ImGui.End(); // if window is collapsed } + private void DrawBoneHierarchy(Snooper s) + { + if (ImGui.Begin("Bone Hierarchy", ref _bh_open, ImGuiWindowFlags.NoScrollbar) && s.Renderer.Options.TryGetModel(out var model)) + { + model.Skeleton.ImGuiBoneHierarchy(); + } + ImGui.End(); // if window is collapsed + } + private void Draw3DViewport(Snooper s) { ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero); diff --git a/FModel/Views/Snooper/Snooper.cs b/FModel/Views/Snooper/Snooper.cs index 7a2711d6..f51ebb19 100644 --- a/FModel/Views/Snooper/Snooper.cs +++ b/FModel/Views/Snooper/Snooper.cs @@ -113,7 +113,6 @@ public class Snooper : GameWindow private void ClearWhatHasBeenDrawn() { GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit); - GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill); } protected override void OnRenderFrame(FrameEventArgs args)