diff --git a/FModel/Views/Snooper/Models/Animations/BoneIndice.cs b/FModel/Views/Snooper/Models/Animations/BoneIndice.cs index f8d9fc16..b4094bf7 100644 --- a/FModel/Views/Snooper/Models/Animations/BoneIndice.cs +++ b/FModel/Views/Snooper/Models/Animations/BoneIndice.cs @@ -11,6 +11,4 @@ public class BoneIndice public int ParentTrackIndex = -1; // bone index of the first tracked parent bone public bool HasTrack => TrackIndex > -1; public bool HasParentTrack => ParentTrackIndex > -1; - - public override string ToString() => $"{ParentBoneIndex} -> {BoneIndex}{(HasTrack ? $" ({ParentTrackIndex} -> {TrackIndex})" : "")}"; } diff --git a/FModel/Views/Snooper/Models/Model.cs b/FModel/Views/Snooper/Models/Model.cs index 133d4127..2fa29933 100644 --- a/FModel/Views/Snooper/Models/Model.cs +++ b/FModel/Views/Snooper/Models/Model.cs @@ -83,8 +83,8 @@ public class Model : IDisposable public bool HasSkeleton => Skeleton != null; public readonly Skeleton Skeleton; - public bool HasSockets => Sockets.Length > 0; - public readonly Socket[] Sockets; + public bool HasSockets => Sockets.Count > 0; + public readonly List Sockets; public bool HasMorphTargets => Morphs.Length > 0; public readonly Morph[] Morphs; @@ -115,7 +115,7 @@ public class Model : IDisposable Type = export.ExportType; UvCount = 1; Box = new FBox(new FVector(-2f), new FVector(2f)); - Sockets = Array.Empty(); + Sockets = new List(); Morphs = Array.Empty(); Transforms = new List(); } @@ -125,11 +125,10 @@ public class Model : IDisposable { Box = staticMesh.BoundingBox * Constants.SCALE_DOWN_RATIO; - Sockets = new Socket[export.Sockets.Length]; - for (int i = 0; i < Sockets.Length; i++) + for (int i = 0; i < export.Sockets.Length; i++) { if (export.Sockets[i].Load() is not { } socket) continue; - Sockets[i] = new Socket(socket); + Sockets.Add(new Socket(socket)); } } @@ -147,11 +146,10 @@ public class Model : IDisposable sockets.AddRange(skeleton.Sockets); } - Sockets = new Socket[sockets.Count]; - for (int i = 0; i < Sockets.Length; i++) + for (int i = 0; i < sockets.Count; i++) { if (sockets[i].Load() is not { } socket) continue; - Sockets[i] = new Socket(socket); + Sockets.Add(new Socket(socket)); } Morphs = new Morph[export.MorphTargets.Length]; @@ -453,10 +451,11 @@ public class Model : IDisposable _matrixVbo.Dispose(); _vao.Dispose(); Skeleton?.Dispose(); - for (int socket = 0; socket < Sockets.Length; socket++) + for (int socket = 0; socket < Sockets.Count; socket++) { Sockets[socket]?.Dispose(); } + Sockets.Clear(); if (HasMorphTargets) _morphVbo.Dispose(); for (var morph = 0; morph < Morphs.Length; morph++) { diff --git a/FModel/Views/Snooper/Models/Socket.cs b/FModel/Views/Snooper/Models/Socket.cs index b9f6c1d0..94d6a7d5 100644 --- a/FModel/Views/Snooper/Models/Socket.cs +++ b/FModel/Views/Snooper/Models/Socket.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using CUE4Parse.UE4.Assets.Exports.SkeletalMesh; using CUE4Parse.UE4.Assets.Exports.StaticMesh; @@ -21,6 +21,13 @@ public class Socket : IDisposable AttachedModels = new List(); } + public Socket(string name, FName boneName, Transform transform) : this() + { + Name = name; + BoneName = boneName; + Transform = transform; + } + public Socket(UStaticMeshSocket socket) : this() { Name = socket.SocketName.Text; diff --git a/FModel/Views/Snooper/Renderer.cs b/FModel/Views/Snooper/Renderer.cs index 247df8a6..a2fc1aad 100644 --- a/FModel/Views/Snooper/Renderer.cs +++ b/FModel/Views/Snooper/Renderer.cs @@ -93,16 +93,60 @@ public class Renderer : IDisposable public void Animate(UObject anim) { - if (!Options.TryGetModel(out var model) || !model.HasSkeleton) + if (!Options.TryGetModel(out var model)) return; - + Animate(anim, model); + } + private void Animate(UObject anim, Model model) + { + if (!model.HasSkeleton) return; switch (anim) { case UAnimSequence animSequence when animSequence.Skeleton.TryLoad(out USkeleton skeleton): model.Skeleton.SetAnimation(skeleton.ConvertAnims(animSequence), AnimateWithRotationOnly); break; case UAnimMontage animMontage when animMontage.Skeleton.TryLoad(out USkeleton skeleton): + // for (int i = 0; i < skeleton.Sockets.Length; i++) + // { + // if (skeleton.Sockets[i].Load() is not { } socket) continue; + // model.Sockets.Add(new Socket(socket)); + // } + model.Skeleton.SetAnimation(skeleton.ConvertAnims(animMontage), AnimateWithRotationOnly); + foreach (var notifyEvent in animMontage.Notifies) + { + if (!notifyEvent.NotifyStateClass.TryLoad(out UObject notifyClass) || + !notifyClass.TryGetValue(out FPackageIndex meshProp, "SkeletalMeshProp", "StaticMeshProp", "Mesh") || + !meshProp.TryLoad(out UObject export)) continue; + + FGuid guid = export switch + { + UStaticMesh st => LoadStaticMesh(st, false), + USkeletalMesh sk => LoadSkeletalMesh(sk, false), + _ => throw new ArgumentException() + }; + + if (!Options.TryGetModel(guid, out var addedModel)) + continue; + + if (notifyClass.TryGetValue(out UObject skeletalMeshPropAnimation, "SkeletalMeshPropAnimation")) + Animate(skeletalMeshPropAnimation, addedModel); + if (notifyClass.TryGetValue(out FName socketName, "SocketName")) + { + var t = Transform.Identity; + if (notifyClass.TryGetValue(out FVector location, "Location")) + t.Position = location * Constants.SCALE_DOWN_RATIO; + if (notifyClass.TryGetValue(out FRotator rotation, "RotationOffset", "Rotation")) + t.Rotation = rotation.Quaternion(); + if (notifyClass.TryGetValue(out FVector scale, "Scale")) + t.Scale = scale; + + var s = new Socket("hello", socketName, t); + model.Sockets.Add(s); + s.AttachedModels.Add(guid); + addedModel.AttachModel(model, s); + } + } break; case UAnimComposite animComposite when animComposite.Skeleton.TryLoad(out USkeleton skeleton): model.Skeleton.SetAnimation(skeleton.ConvertAnims(animComposite), AnimateWithRotationOnly); @@ -168,32 +212,40 @@ public class Renderer : IDisposable Picking.Render(viewMatrix, projMatrix, Options.Models); } - private void LoadStaticMesh(UStaticMesh original) + private FGuid LoadStaticMesh(UStaticMesh original, bool select = true) { var guid = original.LightingGuid; if (Options.TryGetModel(guid, out var model)) { model.AddInstance(Transform.Identity); Application.Current.Dispatcher.Invoke(() => model.SetupInstances()); - return; + return guid; } if (!original.TryConvert(out var mesh)) - return; + return guid; Options.Models[guid] = new Model(original, mesh); - Options.SelectModel(guid); - SetupCamera(Options.Models[guid].Box); + if (select) + { + Options.SelectModel(guid); + SetupCamera(Options.Models[guid].Box); + } + return guid; } - private void LoadSkeletalMesh(USkeletalMesh original) + private FGuid LoadSkeletalMesh(USkeletalMesh original, bool select = true) { var guid = new FGuid((uint) original.GetFullName().GetHashCode()); - if (Options.Models.ContainsKey(guid) || !original.TryConvert(out var mesh)) return; + if (Options.Models.ContainsKey(guid) || !original.TryConvert(out var mesh)) return guid; Options.Models[guid] = new Model(original, mesh); - Options.SelectModel(guid); - SetupCamera(Options.Models[guid].Box); + if (select) + { + Options.SelectModel(guid); + SetupCamera(Options.Models[guid].Box); + } + return guid; } private void LoadMaterialInstance(UMaterialInstance original) diff --git a/FModel/Views/Snooper/SnimGui.cs b/FModel/Views/Snooper/SnimGui.cs index c36d2fbc..a3b90299 100644 --- a/FModel/Views/Snooper/SnimGui.cs +++ b/FModel/Views/Snooper/SnimGui.cs @@ -411,7 +411,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio foreach (var model in s.Renderer.Options.Models.Values) { if (!model.HasSockets || model.IsSelected) continue; - if (ImGui.TreeNode($"{model.Name} [{model.Sockets.Length}]")) + if (ImGui.TreeNode($"{model.Name} [{model.Sockets.Count}]")) { var i = 0; foreach (var socket in model.Sockets) @@ -460,7 +460,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio { Layout("Two Sided");ImGui.Text($" : {model.TwoSided}"); } - Layout("Sockets");ImGui.Text($" : x{model.Sockets.Length}"); + Layout("Sockets");ImGui.Text($" : x{model.Sockets.Count}"); ImGui.EndTable(); });