diff --git a/CUE4Parse b/CUE4Parse index d6bd6e58..581d29b2 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit d6bd6e58cd084ab5fadd46a433bfbe95316e4890 +Subproject commit 581d29b275a2581c2b246991fe8e5f988605e0b5 diff --git a/FModel/Views/Snooper/Models/Animations/Skeleton.cs b/FModel/Views/Snooper/Models/Animations/Skeleton.cs index 72140ed8..d03e610c 100644 --- a/FModel/Views/Snooper/Models/Animations/Skeleton.cs +++ b/FModel/Views/Snooper/Models/Animations/Skeleton.cs @@ -18,13 +18,15 @@ public class Skeleton : IDisposable public readonly Dictionary BonesTransformByIndex; public readonly bool IsLoaded; - public readonly Socket[] Sockets; - public Animation Anim; - private FVector _previousMatrix; + public Skeleton() + { + BonesIndexByName = new Dictionary(); + BonesTransformByIndex = new Dictionary(); + } - public Skeleton(FPackageIndex package, Transform transform) + public Skeleton(FPackageIndex package, Transform transform) : this() { UnrealSkeleton = package.Load(); if (UnrealSkeleton == null) return; @@ -64,22 +66,6 @@ public Skeleton(FPackageIndex package, Transform transform) transforms.Clear(); } IsLoaded = true; - - Sockets = new Socket[UnrealSkeleton.Sockets.Length]; - for (int i = 0; i < Sockets.Length; i++) - { - if (UnrealSkeleton.Sockets[i].Load() is not { } socket) continue; - - if (!BonesIndexByName.TryGetValue(socket.BoneName.Text, out var boneIndex) || - !BonesTransformByIndex.TryGetValue(boneIndex, out var boneTransform)) - { - Sockets[i] = new Socket(socket); - } - else - { - Sockets[i] = new Socket(socket, boneTransform); - } - } } public void SetAnimation(CAnimSet anim) @@ -87,20 +73,13 @@ public void SetAnimation(CAnimSet anim) Anim = new Animation(anim, BonesIndexByName, BonesTransformByIndex); } - public void UpdateSocketsMatrix(Transform t) + public void UpdateRootBoneMatrix(Matrix4x4 delta) { - var m = t.Position; - if (m == _previousMatrix) return; + // Matrix4x4.Decompose(delta, out var scale, out var rotation, out var position); + // Log.Logger.Information("Update"); - var delta = _previousMatrix - m; - Log.Logger.Information("Update {0}", delta); - - // BonesTransformByIndex[0].Relation.Translation += delta; - foreach (var socket in Sockets) - { - socket.Transform.Relation.Translation += delta; - } - _previousMatrix = m; + // TODO: support for rotation and scale + BonesTransformByIndex[0].Relation.Translation += delta.Translation; } public void SetUniform(Shader shader) diff --git a/FModel/Views/Snooper/Models/Model.cs b/FModel/Views/Snooper/Models/Model.cs index 39c2695f..723e9b68 100644 --- a/FModel/Views/Snooper/Models/Model.cs +++ b/FModel/Views/Snooper/Models/Model.cs @@ -52,8 +52,12 @@ public class Model : IDisposable public bool HasSkeleton => Skeleton is { IsLoaded: true }; public readonly Skeleton Skeleton; + public bool HasSockets => Sockets.Length > 0; + public readonly Socket[] Sockets; + public int TransformsCount; public readonly List Transforms; + private Matrix4x4 _previousMatrix; public readonly Morph[] Morphs; @@ -76,14 +80,40 @@ protected Model(UObject export) } 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.Count, staticMesh.LODs[0], staticMesh.LODs[0].Verts, transform) { Box = staticMesh.BoundingBox * Constants.SCALE_DOWN_RATIO; + + Sockets = new Socket[export.Sockets.Length]; + for (int i = 0; i < Sockets.Length; i++) + { + if (export.Sockets[i].Load() is not { } socket) continue; + Sockets[i] = new Socket(socket); + } } private Model(USkeletalMesh export, CSkeletalMesh skeletalMesh, Transform transform) : this(export, export.Materials, export.Skeleton, skeletalMesh.LODs.Count, skeletalMesh.LODs[0], skeletalMesh.LODs[0].Verts, transform) { Box = skeletalMesh.BoundingBox * Constants.SCALE_DOWN_RATIO; + + var sockets = new List(); + sockets.AddRange(export.Sockets); + if (HasSkeleton) sockets.AddRange(Skeleton.UnrealSkeleton.Sockets); + + Sockets = new Socket[sockets.Count]; + for (int i = 0; i < Sockets.Length; i++) + { + if (sockets[i].Load() is not { } socket) continue; + + if (!Skeleton.BonesIndexByName.TryGetValue(socket.BoneName.Text, out var boneIndex) || + !Skeleton.BonesTransformByIndex.TryGetValue(boneIndex, out var boneTransform)) + { + Sockets[i] = new Socket(socket); + } + else + { + Sockets[i] = new Socket(socket, boneTransform); + } + } } public Model(USkeletalMesh export, CSkeletalMesh skeletalMesh) : this(export, skeletalMesh, Transform.Identity) { @@ -194,20 +224,44 @@ public void AddInstance(Transform transform) { TransformsCount++; Transforms.Add(transform); + _previousMatrix = transform.Matrix; } - public void UpdateMatrix() => UpdateMatrix(SelectedInstance); - public void UpdateMatrix(Transform transform) => UpdateMatrix(SelectedInstance, transform); - public void UpdateMatrix(int instance, Transform transform) + public void UpdateMatrices(Options options) { - Transforms[instance] = transform; - UpdateMatrix(instance); + UpdateMatrices(); + if (!HasSkeleton) + return; + + for (int s = 0; s < Sockets.Length; s++) + { + for (int g = 0; g < Sockets[s].AttachedModels.Count; g++) + { + if (!options.TryGetModel(Sockets[s].AttachedModels[g], out var attachedModel)) + continue; + + attachedModel.Transforms[attachedModel.SelectedInstance].Relation = Sockets[s].Transform.Matrix; + attachedModel.UpdateMatrices(); + } + } } - public void UpdateMatrix(int instance) + private void UpdateMatrices() { + var matrix = Transforms[SelectedInstance].Matrix; + if (matrix == _previousMatrix) return; + _matrixVbo.Bind(); - _matrixVbo.Update(instance, Transforms[instance].Matrix); + _matrixVbo.Update(SelectedInstance, matrix); _matrixVbo.Unbind(); + + var delta = matrix - _previousMatrix; + foreach (var socket in Sockets) + { + socket.UpdateSocketMatrix(delta); + } + if (HasSkeleton) Skeleton.UpdateRootBoneMatrix(delta); + + _previousMatrix = matrix; } public void UpdateMorph(int index) diff --git a/FModel/Views/Snooper/Models/Socket.cs b/FModel/Views/Snooper/Models/Socket.cs index a61e453b..c1dd0919 100644 --- a/FModel/Views/Snooper/Models/Socket.cs +++ b/FModel/Views/Snooper/Models/Socket.cs @@ -1,5 +1,8 @@ using System; +using System.Collections.Generic; +using System.Numerics; using CUE4Parse.UE4.Assets.Exports.SkeletalMesh; +using CUE4Parse.UE4.Assets.Exports.StaticMesh; using CUE4Parse.UE4.Objects.Core.Misc; namespace FModel.Views.Snooper.Models; @@ -10,29 +13,48 @@ public class Socket : IDisposable public readonly string Bone; public readonly Transform Transform; - public FGuid AttachedModel; + public readonly List AttachedModels; - public Socket(USkeletalMeshSocket socket) + private Socket() + { + Bone = "None"; + Transform = Transform.Identity; + AttachedModels = new List(); + } + + public Socket(UStaticMeshSocket socket) : this() + { + Name = socket.SocketName.Text; + Transform.Rotation = socket.RelativeRotation.Quaternion(); + Transform.Position = socket.RelativeLocation * Constants.SCALE_DOWN_RATIO; + Transform.Scale = socket.RelativeScale; + } + + public Socket(USkeletalMeshSocket socket) : this() { Name = socket.SocketName.Text; Bone = socket.BoneName.Text; - Transform = Transform.Identity; Transform.Rotation = socket.RelativeRotation.Quaternion(); Transform.Position = socket.RelativeLocation * Constants.SCALE_DOWN_RATIO; Transform.Scale = socket.RelativeScale; } - public Socket(USkeletalMeshSocket socket, Transform transform) + public Socket(USkeletalMeshSocket socket, Transform transform) : this() { Name = socket.SocketName.Text; Bone = socket.BoneName.Text; - Transform = Transform.Identity; Transform.Relation = transform.Matrix; Transform.Rotation = socket.RelativeRotation.Quaternion(); Transform.Position = socket.RelativeLocation * Constants.SCALE_DOWN_RATIO; Transform.Scale = socket.RelativeScale; } + public void UpdateSocketMatrix(Matrix4x4 delta) + { + // TODO: support for rotation and scale + Transform.Relation.Translation += delta.Translation; + } + public void Dispose() { throw new NotImplementedException(); diff --git a/FModel/Views/Snooper/Renderer.cs b/FModel/Views/Snooper/Renderer.cs index c23d2e3f..8ff7f0c3 100644 --- a/FModel/Views/Snooper/Renderer.cs +++ b/FModel/Views/Snooper/Renderer.cs @@ -124,6 +124,7 @@ public void Render() // render model pass foreach (var model in Options.Models.Values) { + model.UpdateMatrices(Options); if (!model.Show) continue; model.Render(_shader); } diff --git a/FModel/Views/Snooper/SnimGui.cs b/FModel/Views/Snooper/SnimGui.cs index f52caca3..55ec13e0 100644 --- a/FModel/Views/Snooper/SnimGui.cs +++ b/FModel/Views/Snooper/SnimGui.cs @@ -394,27 +394,33 @@ private void DrawOuliner(Snooper s) private void DrawSockets(Snooper s) { + var selectedGuid = s.Renderer.Options.SelectedModel; + if (!s.Renderer.Options.TryGetModel(out var selectedModel)) + return; + foreach (var model in s.Renderer.Options.Models.Values) { - if (!model.HasSkeleton || model.IsSelected) return; - if (ImGui.TreeNode($"{model.Name} [{model.Skeleton.Sockets.Length}]")) + if (!model.HasSockets || model.IsSelected) continue; + if (ImGui.TreeNode($"{model.Name} [{model.Sockets.Length}]")) { var i = 0; - foreach (var socket in model.Skeleton.Sockets) + foreach (var socket in model.Sockets) { ImGui.PushID(i); - ImGui.Text($"'{socket.Name}' attached to '{socket.Bone}'"); - ImGui.Text($"P: {socket.Transform.Matrix.Translation.X} | {socket.Transform.Matrix.Translation.Y} | {socket.Transform.Matrix.Translation.Z}"); - if (ImGui.Button("Attach") && s.Renderer.Options.TryGetModel(out var selected)) + switch (socket.AttachedModels.Contains(selectedGuid)) { - socket.AttachedModel = s.Renderer.Options.SelectedModel; - selected.UpdateMatrix(new Transform - { - Relation = socket.Transform.Matrix, - Position = FVector.ZeroVector, - Rotation = new FQuat(0), - Scale = FVector.OneVector - }); + case false when ImGui.Button($"Attach to '{socket.Name}'"): + socket.AttachedModels.Add(selectedGuid); + // reset PRS to 0 so it's attached to the actual position (can be transformed relative to the socket later by the user) + selectedModel.Transforms[selectedModel.SelectedInstance].Position = FVector.ZeroVector; + selectedModel.Transforms[selectedModel.SelectedInstance].Rotation = FQuat.Identity; + selectedModel.Transforms[selectedModel.SelectedInstance].Scale = FVector.OneVector; + break; + case true when ImGui.Button($"Detach from '{socket.Name}'"): + socket.AttachedModels.Remove(selectedGuid); + // reset PRS relation to O + selectedModel.Transforms[selectedModel.SelectedInstance].Relation = Matrix4x4.Identity; + break; } ImGui.PopID(); i++; @@ -422,6 +428,20 @@ private void DrawSockets(Snooper s) ImGui.TreePop(); } } + + // if (ImGui.BeginTable("socket_editor", 2)) + // { + // Layout("Models"); + // ImGui.PushID(1); + // ImGui.Combo("", ref m, "Fly Cam\0Arcball\0"); + // ImGui.PopID(); + // + // Layout("Attach To");ImGui.PushID(2); + // ImGui.Combo("world_mode", ref m, "Fly Cam\0Arcball\0"); + // ImGui.PopID(); + // + // ImGui.EndTable(); + // } } private void DrawDetails(Snooper s) @@ -437,12 +457,12 @@ private void DrawDetails(Snooper s) { Layout("Skeleton");ImGui.Text($" : {model.Skeleton.UnrealSkeleton.Name}"); Layout("Bones");ImGui.Text($" : x{model.Skeleton.UnrealSkeleton.BoneTree.Length}"); - Layout("Sockets");ImGui.Text($" : x{model.Skeleton.Sockets.Length}"); } else { Layout("Two Sided");ImGui.Text($" : {model.TwoSided}"); } + Layout("Sockets");ImGui.Text($" : x{model.Sockets.Length}"); ImGui.EndTable(); } @@ -532,7 +552,6 @@ private void DrawDetails(Snooper s) ImGui.EndDisabled(); ImGui.PopID(); model.Transforms[model.SelectedInstance].ImGuiTransform(s.Renderer.CameraOp.Speed / 100f); - model.UpdateMatrix(); ImGui.EndTabItem(); } diff --git a/FModel/Views/Snooper/Transform.cs b/FModel/Views/Snooper/Transform.cs index cfcc6eb5..fc66d2e6 100644 --- a/FModel/Views/Snooper/Transform.cs +++ b/FModel/Views/Snooper/Transform.cs @@ -59,6 +59,7 @@ public void ImGuiTransform(float speed) ImGui.TreePop(); } + ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); if (ImGui.TreeNode("Scale")) { ImGui.SetNextItemWidth(width);