From 841f40e32bc606866047ff894f1db5305253d1bc Mon Sep 17 00:00:00 2001 From: Asval Date: Sun, 20 Aug 2023 03:54:39 +0200 Subject: [PATCH] skeleton tree + fixed skeleton bones incorrect relation --- CUE4Parse | 2 +- FModel/FModel.csproj | 4 + FModel/Resources/bone.frag | 10 + FModel/Resources/bone.vert | 23 + FModel/Views/Snooper/Animations/Animation.cs | 12 +- FModel/Views/Snooper/Animations/Bone.cs | 2 +- FModel/Views/Snooper/Animations/Skeleton.cs | 39 +- .../Views/Snooper/Buffers/PickingTexture.cs | 6 +- FModel/Views/Snooper/Models/Attachment.cs | 57 +++ FModel/Views/Snooper/Models/BoneModel.cs | 52 ++ FModel/Views/Snooper/Models/EAttribute.cs | 14 + .../Views/Snooper/Models/IRenderableModel.cs | 38 ++ FModel/Views/Snooper/Models/Model.cs | 472 ------------------ FModel/Views/Snooper/Models/SkeletalModel.cs | 99 ++++ .../Models/{Cube.cs => StaticModel.cs} | 20 +- FModel/Views/Snooper/Models/UModel.cs | 380 ++++++++++++++ FModel/Views/Snooper/Options.cs | 61 +-- FModel/Views/Snooper/Renderer.cs | 67 ++- FModel/Views/Snooper/Shading/Material.cs | 2 +- FModel/Views/Snooper/SnimGui.cs | 76 +-- 20 files changed, 843 insertions(+), 593 deletions(-) create mode 100644 FModel/Resources/bone.frag create mode 100644 FModel/Resources/bone.vert create mode 100644 FModel/Views/Snooper/Models/Attachment.cs create mode 100644 FModel/Views/Snooper/Models/BoneModel.cs create mode 100644 FModel/Views/Snooper/Models/EAttribute.cs create mode 100644 FModel/Views/Snooper/Models/IRenderableModel.cs delete mode 100644 FModel/Views/Snooper/Models/Model.cs create mode 100644 FModel/Views/Snooper/Models/SkeletalModel.cs rename FModel/Views/Snooper/Models/{Cube.cs => StaticModel.cs} (67%) create mode 100644 FModel/Views/Snooper/Models/UModel.cs diff --git a/CUE4Parse b/CUE4Parse index d0c44876..e65380d4 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit d0c44876fc718e0893ed699551bc354b8d2a5422 +Subproject commit e65380d4c735ea6222fe53ba8ff1da78aa2329d7 diff --git a/FModel/FModel.csproj b/FModel/FModel.csproj index 07c2fded..5be43802 100644 --- a/FModel/FModel.csproj +++ b/FModel/FModel.csproj @@ -107,6 +107,8 @@ + + @@ -130,6 +132,8 @@ + + diff --git a/FModel/Resources/bone.frag b/FModel/Resources/bone.frag new file mode 100644 index 00000000..316129b3 --- /dev/null +++ b/FModel/Resources/bone.frag @@ -0,0 +1,10 @@ +#version 460 core + +in vec3 fPos; + +out vec4 FragColor; + +void main() +{ + FragColor = vec4(1.0, 1.0, 1.0, 1.0); +} diff --git a/FModel/Resources/bone.vert b/FModel/Resources/bone.vert new file mode 100644 index 00000000..724f01f1 --- /dev/null +++ b/FModel/Resources/bone.vert @@ -0,0 +1,23 @@ +#version 460 core + +layout (location = 1) in vec3 vPos; +layout (location = 9) in mat4 vInstanceMatrix; + +uniform mat4 uView; +uniform mat4 uProjection; +uniform bool uSocket; + +out vec3 fPos; + +void main() +{ + float scale = 0.0075; + mat4 result; + result[0] = vec4(scale, 0.0, 0.0, 0.0); + result[1] = vec4(0.0, scale, 0.0, 0.0); + result[2] = vec4(0.0, 0.0, scale, 0.0); + result[3] = vInstanceMatrix[3]; + + gl_Position = uProjection * uView * result * vec4(vPos, 1.0); + fPos = vec3(result * vec4(vPos, 1.0)); +} diff --git a/FModel/Views/Snooper/Animations/Animation.cs b/FModel/Views/Snooper/Animations/Animation.cs index 14b51c71..35e81597 100644 --- a/FModel/Views/Snooper/Animations/Animation.cs +++ b/FModel/Views/Snooper/Animations/Animation.cs @@ -1,9 +1,13 @@ using System; using System.Collections.Generic; +using System.IO; using System.Numerics; +using CUE4Parse_Conversion; using CUE4Parse_Conversion.Animations.PSA; using CUE4Parse.UE4.Assets.Exports; using CUE4Parse.UE4.Objects.Core.Misc; +using FModel.Settings; +using FModel.Views.Snooper.Models; using ImGuiNET; namespace FModel.Views.Snooper.Animations; @@ -118,11 +122,11 @@ public class Animation : IDisposable foreach ((var guid, var model) in s.Renderer.Options.Models) { var selected = AttachedModels.Contains(guid); - if (ImGui.MenuItem(model.Name, null, selected, (model.HasSkeleton && !model.Skeleton.IsAnimated) || selected)) + if (model is SkeletalModel skeletalModel && ImGui.MenuItem(model.Name, null, selected, !skeletalModel.Skeleton.IsAnimated || selected)) { if (selected) AttachedModels.Remove(guid); else AttachedModels.Add(guid); - model.Skeleton.ResetAnimatedData(true); - if (!selected) model.Skeleton.Animate(UnrealAnim); + skeletalModel.Skeleton.ResetAnimatedData(true); + if (!selected) skeletalModel.Skeleton.Animate(UnrealAnim); } } ImGui.EndMenu(); @@ -130,7 +134,7 @@ public class Animation : IDisposable if (ImGui.MenuItem("Save")) { s.WindowShouldFreeze(true); - saver.Value = s.Renderer.Options.TrySave(_export, out saver.Label, out saver.Path); + saver.Value = new Exporter(_export).TryWriteToDir(new DirectoryInfo(UserSettings.Default.ModelDirectory), out saver.Label, out saver.Path); s.WindowShouldFreeze(false); } ImGui.Separator(); diff --git a/FModel/Views/Snooper/Animations/Bone.cs b/FModel/Views/Snooper/Animations/Bone.cs index dc78c982..e82be630 100644 --- a/FModel/Views/Snooper/Animations/Bone.cs +++ b/FModel/Views/Snooper/Animations/Bone.cs @@ -5,7 +5,7 @@ namespace FModel.Views.Snooper.Animations; public class Bone { public readonly int Index; - public readonly int ParentIndex; + public int ParentIndex; public readonly Transform Rest; public readonly bool IsVirtual; diff --git a/FModel/Views/Snooper/Animations/Skeleton.cs b/FModel/Views/Snooper/Animations/Skeleton.cs index 487fc336..01ae8a69 100644 --- a/FModel/Views/Snooper/Animations/Skeleton.cs +++ b/FModel/Views/Snooper/Animations/Skeleton.cs @@ -28,6 +28,7 @@ public class Skeleton : IDisposable private int TotalBoneCount => BoneCount + _additionalBoneCount; public bool IsAnimated { get; private set; } + public bool DrawAllBones; public string SelectedBone; public Skeleton() @@ -62,6 +63,7 @@ public class Skeleton : IDisposable if (boneIndex == 0) SelectedBone = boneName; BonesByLoweredName[boneName] = bone; } + _breadcrumb.Add(SelectedBone); _boneMatriceAtFrame = new Matrix4x4[BoneCount]; } @@ -74,7 +76,7 @@ public class Skeleton : IDisposable if (!BonesByLoweredName.TryGetValue(boneName, out var bone)) { - bone = new Bone(BoneCount + _additionalBoneCount, info.ParentIndex, new Transform + bone = new Bone(BoneCount + _additionalBoneCount, -1, new Transform { Rotation = referenceSkeleton.FinalRefBonePose[boneIndex].Rotation, Position = referenceSkeleton.FinalRefBonePose[boneIndex].Translation * Constants.SCALE_DOWN_RATIO, @@ -83,9 +85,10 @@ public class Skeleton : IDisposable if (!bone.IsRoot) { - bone.LoweredParentName = referenceSkeleton.FinalRefBoneInfo[bone.ParentIndex].Name.Text.ToLower(); + bone.LoweredParentName = referenceSkeleton.FinalRefBoneInfo[info.ParentIndex].Name.Text.ToLower(); var parentBone = BonesByLoweredName[bone.LoweredParentName]; + bone.ParentIndex = parentBone.Index; bone.Rest.Relation = parentBone.Rest.Matrix; parentBone.LoweredChildNames.Add(boneName); } @@ -241,21 +244,37 @@ public class Skeleton : IDisposable public void ImGuiBoneBreadcrumb() { - for (int i = _breadcrumb.Count - 1; i >= 0; i--) + var p1 = ImGui.GetCursorScreenPos(); + var canvasSize = ImGui.GetContentRegionAvail() with { Y = 20 }; + var p2 = p1 + canvasSize; + ImGui.BeginChild("skeleton_breadcrumb", canvasSize); + + var drawList = ImGui.GetWindowDrawList(); + drawList.AddRectFilled(p1, p2, 0xFF242424); + + var x = p1.X; + var y = p1.Y + (p2.Y - p1.Y) / 2; + for (int i = Math.Min(_breadcrumb.Count - 1, 5); i >= 1; i--) { var boneName = _breadcrumb[i]; - ImGui.SameLine(); - var clicked = ImGui.SmallButton(boneName); - ImGui.SameLine(); - ImGui.Text(">"); + var size = ImGui.CalcTextSize(boneName); + var position = new Vector2(x + 5, y - size.Y / 2f); - if (clicked) + ImGui.SetCursorScreenPos(position); + if (ImGui.InvisibleButton($"breakfast_{boneName}", size, ImGuiButtonFlags.MouseButtonLeft)) { SelectedBone = boneName; - _breadcrumb.RemoveRange(0, i + 1); + _breadcrumb.RemoveRange(0, i); break; } + + drawList.AddText(position, i == 1 || ImGui.IsItemHovered() ? 0xFFFFFFFF : 0xA0FFFFFF, boneName); + x += size.X + 7.5f; + drawList.AddText(position with { X = x }, 0xA0FFFFFF, ">"); + x += 7.5f; } + + ImGui.EndChild(); } public void ImGuiBoneHierarchy() @@ -276,7 +295,7 @@ public class Skeleton : IDisposable ImGui.SetNextItemOpen(bone.LoweredChildNames.Count <= 1 || flags.HasFlag(ImGuiTreeNodeFlags.Selected), ImGuiCond.Appearing); var open = ImGui.TreeNodeEx(boneName, flags); - if (ImGui.IsItemClicked() && !ImGui.IsItemToggledOpen()) + if (ImGui.IsItemClicked() && !ImGui.IsItemToggledOpen() && bone.IsDaron) { SelectedBone = boneName; _breadcrumb.Clear(); diff --git a/FModel/Views/Snooper/Buffers/PickingTexture.cs b/FModel/Views/Snooper/Buffers/PickingTexture.cs index 6a8a0d44..fe45cb7c 100644 --- a/FModel/Views/Snooper/Buffers/PickingTexture.cs +++ b/FModel/Views/Snooper/Buffers/PickingTexture.cs @@ -54,20 +54,20 @@ public class PickingTexture : IDisposable Bind(0); } - public void Render(Matrix4x4 viewMatrix, Matrix4x4 projMatrix, IDictionary models) + public void Render(Matrix4x4 viewMatrix, Matrix4x4 projMatrix, IDictionary models) { Bind(); GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); _shader.Render(viewMatrix, projMatrix); - foreach ((FGuid guid, Model model) in models) + foreach ((var guid, var model) in models) { _shader.SetUniform("uA", guid.A); _shader.SetUniform("uB", guid.B); _shader.SetUniform("uC", guid.C); _shader.SetUniform("uD", guid.D); - if (!model.Show) continue; + if (!model.IsVisible) continue; model.PickingRender(_shader); } diff --git a/FModel/Views/Snooper/Models/Attachment.cs b/FModel/Views/Snooper/Models/Attachment.cs new file mode 100644 index 00000000..6e955e4a --- /dev/null +++ b/FModel/Views/Snooper/Models/Attachment.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using System.Numerics; +using CUE4Parse.UE4.Objects.Core.Math; + +namespace FModel.Views.Snooper.Models; + +public class Attachment +{ + private string _modelName; + private string _attachedTo; + private readonly List _attachedFor; + private Matrix4x4 _oldRelation; + + public bool IsAttached => _attachedTo.Length > 0; + public bool IsAttachment => _attachedFor.Count > 0; + + public string Icon => IsAttachment ? "link_has" : IsAttached ? "link_on" : "link_off"; + public string Tooltip => IsAttachment ? $"Is Attachment For:\n{string.Join("\n", _attachedFor)}" : IsAttached ? $"Is Attached To {_attachedTo}" : "Not Attached To Any Socket Nor Attachment For Any Model"; + + public Attachment(string modelName) + { + _modelName = modelName; + _attachedTo = string.Empty; + _attachedFor = new List(); + } + + public void Attach(UModel attachedTo, Transform transform, Socket socket, SocketAttachementInfo info) + { + socket.AttachedModels.Add(info); + + _attachedTo = $"'{socket.Name}' from '{attachedTo.Name}'{(!socket.BoneName.IsNone ? $" at '{socket.BoneName}'" : "")}"; + attachedTo.Attachments.AddAttachment(_modelName); + + // reset PRS to 0 so it's attached to the actual position (can be transformed relative to the socket later by the user) + _oldRelation = transform.Relation; + transform.Position = FVector.ZeroVector; + transform.Rotation = FQuat.Identity; + transform.Scale = FVector.OneVector; + } + + public void Detach(UModel attachedTo, Transform transform, Socket socket, SocketAttachementInfo info) + { + socket.AttachedModels.Remove(info); + SafeDetach(attachedTo, transform); + } + + public void SafeDetach(UModel attachedTo, Transform transform) + { + _attachedTo = string.Empty; + attachedTo.Attachments.RemoveAttachment(_modelName); + + transform.Relation = _oldRelation; + } + + public void AddAttachment(string modelName) => _attachedFor.Add($"'{modelName}'"); + public void RemoveAttachment(string modelName) => _attachedFor.Remove($"'{modelName}'"); +} diff --git a/FModel/Views/Snooper/Models/BoneModel.cs b/FModel/Views/Snooper/Models/BoneModel.cs new file mode 100644 index 00000000..65870c98 --- /dev/null +++ b/FModel/Views/Snooper/Models/BoneModel.cs @@ -0,0 +1,52 @@ +using CUE4Parse_Conversion.Meshes.PSK; +using FModel.Views.Snooper.Shading; +using OpenTK.Graphics.OpenGL4; + +namespace FModel.Views.Snooper.Models; + +public class BoneModel : UModel +{ + public BoneModel(CStaticMesh staticMesh) + { + var lod = staticMesh.LODs[LodLevel]; + + Indices = new uint[lod.Indices.Value.Length]; + for (int i = 0; i < Indices.Length; i++) + { + Indices[i] = (uint) lod.Indices.Value[i]; + } + + Vertices = new float[lod.NumVerts * VertexSize]; + for (int i = 0; i < lod.Verts.Length; i++) + { + var count = 0; + var baseIndex = i * VertexSize; + var vert = lod.Verts[i]; + Vertices[baseIndex + count++] = vert.Position.X * Constants.SCALE_DOWN_RATIO; + Vertices[baseIndex + count++] = vert.Position.Z * Constants.SCALE_DOWN_RATIO; + Vertices[baseIndex + count++] = vert.Position.Y * Constants.SCALE_DOWN_RATIO; + } + + Materials = new Material[1]; + Materials[0] = new Material { IsUsed = true }; + + Sections = new Section[1]; + Sections[0] = new Section(0, Indices.Length, 0); + + Box = staticMesh.BoundingBox * Constants.SCALE_DOWN_RATIO; + } + + public override void Render(Shader shader, bool outline = false) + { + GL.Disable(EnableCap.DepthTest); + + Vao.Bind(); + foreach (var section in Sections) + { + GL.DrawElementsInstanced(PrimitiveType.LineStrip, section.FacesCount, DrawElementsType.UnsignedInt, section.FirstFaceIndexPtr, TransformsCount); + } + Vao.Unbind(); + + GL.Enable(EnableCap.DepthTest); + } +} diff --git a/FModel/Views/Snooper/Models/EAttribute.cs b/FModel/Views/Snooper/Models/EAttribute.cs new file mode 100644 index 00000000..4528a968 --- /dev/null +++ b/FModel/Views/Snooper/Models/EAttribute.cs @@ -0,0 +1,14 @@ +namespace FModel.Views.Snooper.Models; + +public enum EAttribute +{ + Index, + Position, + Normals, + Tangent, + UVs, + Layer, + Colors, + BonesId, + BonesWeight +} diff --git a/FModel/Views/Snooper/Models/IRenderableModel.cs b/FModel/Views/Snooper/Models/IRenderableModel.cs new file mode 100644 index 00000000..a651bcc0 --- /dev/null +++ b/FModel/Views/Snooper/Models/IRenderableModel.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Numerics; +using FModel.Views.Snooper.Buffers; +using FModel.Views.Snooper.Shading; + +namespace FModel.Views.Snooper.Models; + +public interface IRenderableModel : IDisposable +{ + protected int Handle { get; set; } + protected BufferObject Ebo { get; set; } + protected BufferObject Vbo { get; set; } + protected BufferObject MatrixVbo { get; set; } + protected VertexArrayObject Vao { get; set; } + + public string Path { get; } + public string Name { get; } + public string Type { get; } + public int UvCount { get; } + public uint[] Indices { get; protected set; } + public float[] Vertices { get; protected set; } + public Section[] Sections { get; protected set; } + public List Transforms { get; } + public Attachment Attachments { get; } + + public bool IsSetup { get; set; } + public bool IsVisible { get; set; } + public bool IsSelected { get; set; } + public bool ShowWireframe { get; set; } + + public void Setup(Options options); + public void SetupInstances(); + public void Render(Shader shader, bool outline = false); + public void PickingRender(Shader shader); + public void Update(Options options); + public void AddInstance(Transform transform); +} diff --git a/FModel/Views/Snooper/Models/Model.cs b/FModel/Views/Snooper/Models/Model.cs deleted file mode 100644 index b818ff18..00000000 --- a/FModel/Views/Snooper/Models/Model.cs +++ /dev/null @@ -1,472 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using CUE4Parse.UE4.Assets.Exports.Animation; -using CUE4Parse.UE4.Objects.UObject; -using CUE4Parse_Conversion.Meshes.PSK; -using CUE4Parse.UE4.Assets; -using CUE4Parse.UE4.Assets.Exports; -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 FModel.Extensions; -using FModel.Views.Snooper.Animations; -using FModel.Views.Snooper.Buffers; -using FModel.Views.Snooper.Shading; -using OpenTK.Graphics.OpenGL4; - -namespace FModel.Views.Snooper.Models; - -public class VertexAttribute -{ - public int Size; - public bool Enabled; -} - -public enum EAttribute -{ - Index, - Position, - Normals, - Tangent, - UVs, - Layer, - Colors, - BonesId, - BonesWeight -} - -public class Model : IDisposable -{ - private int _handle; - private const int _LOD_INDEX = 0; - - private BufferObject _ebo; - private BufferObject _vbo; - private BufferObject _morphVbo; - private BufferObject _matrixVbo; - private VertexArrayObject _vao; - - private readonly List _vertexAttributes = new() - { - new VertexAttribute { Size = 1, Enabled = true }, // VertexIndex - new VertexAttribute { Size = 3, Enabled = true }, // Position - new VertexAttribute { Size = 3, Enabled = true }, // Normal - new VertexAttribute { Size = 3, Enabled = true }, // Tangent - new VertexAttribute { Size = 2, Enabled = true }, // UV - new VertexAttribute { Size = 1, Enabled = true }, // TextureLayer - new VertexAttribute { Size = 4, Enabled = false }, // Colors - new VertexAttribute { Size = 4, Enabled = false }, // BoneIds - new VertexAttribute { Size = 4, Enabled = false } // BoneWeights - }; - public int VertexSize => _vertexAttributes.Where(x => x.Enabled).Sum(x => x.Size); - public bool HasVertexColors => _vertexAttributes[(int) EAttribute.Colors].Enabled; - private const int _faceSize = 3; - - public readonly UObject Export; - public readonly string Path; - public readonly string Name; - public readonly string Type; - public readonly int UvCount; - public readonly FBox Box; - public uint[] Indices; - public float[] Vertices; - public Section[] Sections; - public Material[] Materials; - public bool TwoSided; - public bool IsAnimatedProp; - - public bool HasSkeleton => Skeleton != null; - public readonly Skeleton Skeleton; - - public bool HasSockets => Sockets.Count > 0; - public readonly List Sockets; - - public bool HasMorphTargets => Morphs.Count > 0; - public readonly List Morphs; - - private string _attachedTo = string.Empty; - private readonly List _attachedFor = new (); - public bool IsAttached => _attachedTo.Length > 0; - public bool IsAttachment => _attachedFor.Count > 0; - public string AttachIcon => IsAttachment ? "link_has" : IsAttached ? "link_on" : "link_off"; - public string AttachTooltip => IsAttachment ? $"Is Attachment For:\n{string.Join("\n", _attachedFor)}" : IsAttached ? $"Is Attached To {_attachedTo}" : "Not Attached To Any Socket Nor Attachment For Any Model"; - - public int TransformsCount; - public readonly List Transforms; - private Matrix4x4 _previousMatrix; - - public bool Show; - public bool Wireframe; - public bool IsSetup { get; private set; } - public bool IsSelected; - public int SelectedInstance; - public float MorphTime; - - protected Model(UObject export) - { - Export = export; - Path = Export.GetPathName(); - Name = Path.SubstringAfterLast('/').SubstringBefore('.'); - Type = export.ExportType; - UvCount = 1; - Box = new FBox(new FVector(-2f), new FVector(2f)); - Sockets = new List(); - Morphs = new List(); - Transforms = new List(); - } - - 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; - - for (int i = 0; i < export.Sockets.Length; i++) - { - if (export.Sockets[i].Load() is not { } socket) continue; - Sockets.Add(new Socket(socket)); - } - } - - 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); - - var sockets = new List(); - sockets.AddRange(export.Sockets); - if (HasSkeleton && export.Skeleton.TryLoad(out USkeleton skeleton)) - { - Skeleton.Name = skeleton.Name; - // Skeleton.Merge(skeleton.ReferenceSkeleton); - sockets.AddRange(skeleton.Sockets); - } - - for (int i = 0; i < sockets.Count; i++) - { - if (sockets[i].Load() is not { } socket) continue; - Sockets.Add(new Socket(socket)); - } - - for (var i = 0; i < export.MorphTargets.Length; i++) - { - if (!export.MorphTargets[i].TryLoad(out UMorphTarget morphTarget) || - morphTarget.MorphLODModels.Length < 1 || morphTarget.MorphLODModels[0].Vertices.Length < 1) - continue; - - Morphs.Add(new Morph(Vertices, VertexSize, morphTarget)); - } - } - - 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; - TwoSided = lod.IsTwoSided; - - Materials = new Material[materials.Count]; - for (int m = 0; m < Materials.Length; m++) - { - if ((materials[m]?.TryLoad(out var material) ?? false) && material is UMaterialInterface unrealMaterial) - Materials[m] = new Material(unrealMaterial); else Materials[m] = new Material(); - } - - _vertexAttributes[(int) EAttribute.Colors].Enabled = lod.VertexColors is { Length: > 0}; - _vertexAttributes[(int) EAttribute.BonesId].Enabled = - _vertexAttributes[(int) EAttribute.BonesWeight].Enabled = vertices is CSkelMeshVertex[]; - - Indices = new uint[lod.Indices.Value.Length]; - for (int i = 0; i < Indices.Length; i++) - { - Indices[i] = (uint) lod.Indices.Value[i]; - } - - Vertices = new float[lod.NumVerts * VertexSize]; - for (int i = 0; i < vertices.Count; i++) - { - var count = 0; - var baseIndex = i * VertexSize; - var vert = vertices[i]; - Vertices[baseIndex + count++] = i; - Vertices[baseIndex + count++] = vert.Position.X * Constants.SCALE_DOWN_RATIO; - Vertices[baseIndex + count++] = vert.Position.Z * Constants.SCALE_DOWN_RATIO; - Vertices[baseIndex + count++] = vert.Position.Y * Constants.SCALE_DOWN_RATIO; - Vertices[baseIndex + count++] = vert.Normal.X; - Vertices[baseIndex + count++] = vert.Normal.Z; - Vertices[baseIndex + count++] = vert.Normal.Y; - Vertices[baseIndex + count++] = vert.Tangent.X; - Vertices[baseIndex + count++] = vert.Tangent.Z; - Vertices[baseIndex + count++] = vert.Tangent.Y; - Vertices[baseIndex + count++] = vert.UV.U; - Vertices[baseIndex + count++] = vert.UV.V; - Vertices[baseIndex + count++] = hasCustomUvs ? lod.ExtraUV.Value[0][i].U : .5f; - - if (HasVertexColors) - { - var color = lod.VertexColors[i]; - Vertices[baseIndex + count++] = color.R; - Vertices[baseIndex + count++] = color.G; - Vertices[baseIndex + count++] = color.B; - Vertices[baseIndex + count++] = color.A; - } - - if (vert is CSkelMeshVertex skelVert) - { - var weightsHash = skelVert.UnpackWeights(); - Vertices[baseIndex + count++] = skelVert.Bone[0]; - Vertices[baseIndex + count++] = skelVert.Bone[1]; - Vertices[baseIndex + count++] = skelVert.Bone[2]; - Vertices[baseIndex + count++] = skelVert.Bone[3]; - Vertices[baseIndex + count++] = weightsHash[0]; - Vertices[baseIndex + count++] = weightsHash[1]; - Vertices[baseIndex + count++] = weightsHash[2]; - Vertices[baseIndex + count++] = weightsHash[3]; - } - } - - Sections = new Section[lod.Sections.Value.Length]; - for (var s = 0; s < Sections.Length; s++) - { - var section = lod.Sections.Value[s]; - Sections[s] = new Section(section.MaterialIndex, section.NumFaces * _faceSize, section.FirstIndex); - if (section.IsValid) Sections[s].SetupMaterial(Materials[section.MaterialIndex]); - } - - var t = transform ?? Transform.Identity; - _previousMatrix = t.Matrix; - AddInstance(t); - } - - public void AddInstance(Transform transform) - { - SelectedInstance = TransformsCount; - TransformsCount++; - Transforms.Add(transform); - } - - public void UpdateMatrices(Options options) - { - 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); - - var socketRelation = boneMatrix * worldMatrix; - foreach (var info in socket.AttachedModels) - { - if (!options.TryGetModel(info.Guid, out var attachedModel)) - continue; - - attachedModel.Transforms[info.Instance].Relation = socket.Transform.LocalMatrix * socketRelation; - attachedModel.UpdateMatrices(options); - } - } - } - private Matrix4x4 UpdateMatrices() - { - _matrixVbo.Bind(); - for (int instance = 0; instance < TransformsCount; instance++) - { - var matrix = Transforms[instance].Matrix; - _matrixVbo.Update(instance, matrix); - _previousMatrix = matrix; - } - _matrixVbo.Unbind(); - return _previousMatrix; - } - - public void UpdateMorph(int index) - { - _morphVbo.Update(Morphs[index].Vertices); - } - - public void AttachModel(Model attachedTo, Socket socket, SocketAttachementInfo info) - { - socket.AttachedModels.Add(info); - - _attachedTo = $"'{socket.Name}' from '{attachedTo.Name}'{(!socket.BoneName.IsNone ? $" at '{socket.BoneName}'" : "")}"; - attachedTo._attachedFor.Add($"'{Name}'"); - // reset PRS to 0 so it's attached to the actual position (can be transformed relative to the socket later by the user) - Transforms[SelectedInstance].Position = FVector.ZeroVector; - Transforms[SelectedInstance].Rotation = FQuat.Identity; - Transforms[SelectedInstance].Scale = FVector.OneVector; - } - - public void DetachModel(Model attachedTo, Socket socket, SocketAttachementInfo info) - { - socket.AttachedModels.Remove(info); - SafeDetachModel(attachedTo); - } - - public void SafeDetachModel(Model attachedTo) - { - _attachedTo = string.Empty; - attachedTo._attachedFor.Remove($"'{Name}'"); - Transforms[SelectedInstance].Relation = _previousMatrix; - } - - public void SetupInstances() - { - var instanceMatrix = new Matrix4x4[TransformsCount]; - for (var i = 0; i < instanceMatrix.Length; i++) - instanceMatrix[i] = Transforms[i].Matrix; - _matrixVbo = new BufferObject(instanceMatrix, BufferTarget.ArrayBuffer); - _vao.BindInstancing(); // VertexAttributePointer - } - - public void Setup(Options options) - { - _handle = GL.CreateProgram(); - var broken = GL.GetInteger(GetPName.MaxTextureCoords) == 0; - - _ebo = new BufferObject(Indices, BufferTarget.ElementArrayBuffer); - _vbo = new BufferObject(Vertices, BufferTarget.ArrayBuffer); - _vao = new VertexArrayObject(_vbo, _ebo); - - var offset = 0; - for (int i = 0; i < _vertexAttributes.Count; i++) - { - var attribute = _vertexAttributes[i]; - if (!attribute.Enabled) continue; - - if (i != 5 || !broken) - { - _vao.VertexAttributePointer((uint) i, attribute.Size, i == 0 ? VertexAttribPointerType.Int : VertexAttribPointerType.Float, VertexSize, offset); - } - offset += attribute.Size; - } - - SetupInstances(); // instanced models transform - - // setup all used materials for use in different UV channels - for (var i = 0; i < Materials.Length; i++) - { - if (!Materials[i].IsUsed) continue; - Materials[i].Setup(options, broken ? 1 : UvCount); - } - - if (HasSkeleton) Skeleton.Setup(); - if (HasMorphTargets) - { - for (int morph = 0; morph < Morphs.Count; morph++) - { - Morphs[morph].Setup(); - if (morph == 0) - _morphVbo = new BufferObject(Morphs[morph].Vertices, BufferTarget.ArrayBuffer); - } - _vao.Bind(); - _vao.VertexAttributePointer(13, 3, VertexAttribPointerType.Float, Morph.VertexSize, 0); // morph position - _vao.VertexAttributePointer(14, 3, VertexAttribPointerType.Float, Morph.VertexSize, 0); // morph tangent - _vao.Unbind(); - } - - if (options.Models.Count == 1 && Sections.All(x => !x.Show)) - { - Show = true; - foreach (var section in Sections) - { - section.Show = true; - } - } - else foreach (var section in Sections) - { - if (!Show) Show = section.Show; - } - - IsSetup = true; - } - - public void Render(Shader shader, bool outline = false) - { - if (outline) GL.Disable(EnableCap.DepthTest); - if (TwoSided) GL.Disable(EnableCap.CullFace); - if (IsSelected) - { - GL.Enable(EnableCap.StencilTest); - GL.StencilFunc(outline ? StencilFunction.Notequal : StencilFunction.Always, 1, 0xFF); - } - - _vao.Bind(); - shader.SetUniform("uMorphTime", MorphTime); - if (HasSkeleton) Skeleton.Render(shader); - if (!outline) - { - shader.SetUniform("uUvCount", UvCount); - shader.SetUniform("uHasVertexColors", HasVertexColors); - } - - GL.PolygonMode(MaterialFace.FrontAndBack, Wireframe ? PolygonMode.Line : PolygonMode.Fill); - foreach (var section in Sections) - { - if (!section.Show) continue; - if (!outline) - { - shader.SetUniform("uSectionColor", section.Color); - Materials[section.MaterialIndex].Render(shader); - } - - GL.DrawElementsInstanced(PrimitiveType.Triangles, section.FacesCount, DrawElementsType.UnsignedInt, section.FirstFaceIndexPtr, TransformsCount); - } - GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill); - _vao.Unbind(); - - if (IsSelected) - { - GL.StencilFunc(StencilFunction.Always, 0, 0xFF); - GL.Disable(EnableCap.StencilTest); - } - if (TwoSided) GL.Enable(EnableCap.CullFace); - if (outline) GL.Enable(EnableCap.DepthTest); - } - - public void PickingRender(Shader shader) - { - if (TwoSided) GL.Disable(EnableCap.CullFace); - - _vao.Bind(); - shader.SetUniform("uMorphTime", MorphTime); - if (HasSkeleton) Skeleton.Render(shader); - - foreach (var section in Sections) - { - if (!section.Show) continue; - GL.DrawElementsInstanced(PrimitiveType.Triangles, section.FacesCount, DrawElementsType.UnsignedInt, section.FirstFaceIndexPtr, TransformsCount); - } - _vao.Unbind(); - - if (TwoSided) GL.Enable(EnableCap.CullFace); - } - - public void Dispose() - { - _ebo?.Dispose(); - _vbo?.Dispose(); - _matrixVbo?.Dispose(); - _vao?.Dispose(); - Skeleton?.Dispose(); - for (int socket = 0; socket < Sockets.Count; socket++) - { - Sockets[socket]?.Dispose(); - } - Sockets.Clear(); - if (HasMorphTargets) _morphVbo.Dispose(); - for (var morph = 0; morph < Morphs.Count; morph++) - { - Morphs[morph]?.Dispose(); - } - Morphs.Clear(); - - GL.DeleteProgram(_handle); - } -} diff --git a/FModel/Views/Snooper/Models/SkeletalModel.cs b/FModel/Views/Snooper/Models/SkeletalModel.cs new file mode 100644 index 00000000..4d5acdd2 --- /dev/null +++ b/FModel/Views/Snooper/Models/SkeletalModel.cs @@ -0,0 +1,99 @@ +using System.Collections.Generic; +using CUE4Parse_Conversion.Meshes.PSK; +using CUE4Parse.UE4.Assets.Exports.Animation; +using CUE4Parse.UE4.Assets.Exports.SkeletalMesh; +using CUE4Parse.UE4.Objects.UObject; +using FModel.Views.Snooper.Animations; +using FModel.Views.Snooper.Buffers; +using FModel.Views.Snooper.Shading; +using OpenTK.Graphics.OpenGL4; + +namespace FModel.Views.Snooper.Models; + +public class SkeletalModel : UModel +{ + private BufferObject _morphVbo; + + public readonly Skeleton Skeleton; + public readonly List Morphs; + + public bool HasMorphTargets => Morphs.Count > 0; + + public float MorphTime; + + public SkeletalModel(USkeletalMesh export, CSkeletalMesh skeletalMesh, Transform transform = null) + : base(export, skeletalMesh.LODs[LodLevel], export.Materials, skeletalMesh.LODs[LodLevel].Verts, skeletalMesh.LODs.Count, transform) + { + Box = skeletalMesh.BoundingBox * Constants.SCALE_DOWN_RATIO; + Skeleton = new Skeleton(export.ReferenceSkeleton); + + var sockets = new List(); + sockets.AddRange(export.Sockets); + if (export.Skeleton.TryLoad(out USkeleton skeleton)) + { + Skeleton.Name = skeleton.Name; + // Skeleton.Merge(skeleton.ReferenceSkeleton); + sockets.AddRange(skeleton.Sockets); + } + + for (int i = 0; i < sockets.Count; i++) + { + if (sockets[i].Load() is not { } socket) continue; + Sockets.Add(new Socket(socket)); + } + + Morphs = new List(); + for (var i = 0; i < export.MorphTargets.Length; i++) + { + if (!export.MorphTargets[i].TryLoad(out UMorphTarget morphTarget) || + morphTarget.MorphLODModels.Length < 1 || morphTarget.MorphLODModels[0].Vertices.Length < 1) + continue; + + Morphs.Add(new Morph(Vertices, VertexSize, morphTarget)); + } + } + + public override void Setup(Options options) + { + base.Setup(options); + + Skeleton.Setup(); + if (!HasMorphTargets) return; + + for (int morph = 0; morph < Morphs.Count; morph++) + { + Morphs[morph].Setup(); + if (morph == 0) + _morphVbo = new BufferObject(Morphs[morph].Vertices, BufferTarget.ArrayBuffer); + } + + Vao.Bind(); + Vao.VertexAttributePointer(13, 3, VertexAttribPointerType.Float, Morph.VertexSize, 0); // morph position + Vao.VertexAttributePointer(14, 3, VertexAttribPointerType.Float, Morph.VertexSize, 0); // morph tangent + Vao.Unbind(); + } + + public void Render(Shader shader) + { + shader.SetUniform("uMorphTime", MorphTime); + Skeleton.Render(shader); + } + + public void UpdateMorph(int index) + { + _morphVbo.Update(Morphs[index].Vertices); + } + + public override void Dispose() + { + Skeleton?.Dispose(); + if (HasMorphTargets) _morphVbo.Dispose(); + foreach (var morph in Morphs) + { + morph?.Dispose(); + } + Morphs.Clear(); + + base.Dispose(); + } +} diff --git a/FModel/Views/Snooper/Models/Cube.cs b/FModel/Views/Snooper/Models/StaticModel.cs similarity index 67% rename from FModel/Views/Snooper/Models/Cube.cs rename to FModel/Views/Snooper/Models/StaticModel.cs index 3681cf08..fd253041 100644 --- a/FModel/Views/Snooper/Models/Cube.cs +++ b/FModel/Views/Snooper/Models/StaticModel.cs @@ -1,14 +1,15 @@ using CUE4Parse_Conversion.Meshes.PSK; using CUE4Parse.UE4.Assets.Exports.Material; +using CUE4Parse.UE4.Assets.Exports.StaticMesh; using FModel.Views.Snooper.Shading; namespace FModel.Views.Snooper.Models; -public class Cube : Model +public class StaticModel : UModel { - public Cube(CStaticMesh mesh, UMaterialInterface unrealMaterial) : base(unrealMaterial) + public StaticModel(UMaterialInterface unrealMaterial, CStaticMesh staticMesh) : base(unrealMaterial) { - var lod = mesh.LODs[0]; + var lod = staticMesh.LODs[LodLevel]; Indices = new uint[lod.Indices.Value.Length]; for (int i = 0; i < Indices.Length; i++) @@ -44,5 +45,18 @@ public class Cube : Model Sections[0] = new Section(0, Indices.Length, 0); AddInstance(Transform.Identity); + + Box = staticMesh.BoundingBox * Constants.SCALE_DOWN_RATIO; + } + + public StaticModel(UStaticMesh export, CStaticMesh staticMesh, Transform transform = null) + : base(export, staticMesh.LODs[LodLevel], export.Materials, staticMesh.LODs[LodLevel].Verts, staticMesh.LODs.Count, transform) + { + Box = staticMesh.BoundingBox * Constants.SCALE_DOWN_RATIO; + for (int i = 0; i < export.Sockets.Length; i++) + { + if (export.Sockets[i].Load() is not { } socket) continue; + Sockets.Add(new Socket(socket)); + } } } diff --git a/FModel/Views/Snooper/Models/UModel.cs b/FModel/Views/Snooper/Models/UModel.cs new file mode 100644 index 00000000..836331ac --- /dev/null +++ b/FModel/Views/Snooper/Models/UModel.cs @@ -0,0 +1,380 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Numerics; +using CUE4Parse_Conversion; +using CUE4Parse_Conversion.Meshes.PSK; +using CUE4Parse.UE4.Assets; +using CUE4Parse.UE4.Assets.Exports; +using CUE4Parse.UE4.Assets.Exports.Material; +using CUE4Parse.UE4.Objects.Core.Math; +using CUE4Parse.Utils; +using FModel.Settings; +using FModel.Views.Snooper.Buffers; +using FModel.Views.Snooper.Shading; +using OpenTK.Graphics.OpenGL4; + +namespace FModel.Views.Snooper.Models; + +public class VertexAttribute +{ + public int Size; + public bool Enabled; +} + +public abstract class UModel : IRenderableModel +{ + protected const int LodLevel = 0; + + private readonly UObject _export; + private readonly List _vertexAttributes = new() + { + new VertexAttribute { Size = 1, Enabled = false }, // VertexIndex + new VertexAttribute { Size = 3, Enabled = true }, // Position + new VertexAttribute { Size = 3, Enabled = false }, // Normal + new VertexAttribute { Size = 3, Enabled = false }, // Tangent + new VertexAttribute { Size = 2, Enabled = false }, // UV + new VertexAttribute { Size = 1, Enabled = false }, // TextureLayer + new VertexAttribute { Size = 4, Enabled = false }, // Colors + new VertexAttribute { Size = 4, Enabled = false }, // BoneIds + new VertexAttribute { Size = 4, Enabled = false } // BoneWeights + }; + + public int Handle { get; set; } + public BufferObject Ebo { get; set; } + public BufferObject Vbo { get; set; } + public BufferObject MatrixVbo { get; set; } + public VertexArrayObject Vao { get; set; } + + public string Path { get; } + public string Name { get; } + public string Type { get; } + public int UvCount { get; } + public uint[] Indices { get; set; } + public float[] Vertices { get; set; } + public Section[] Sections { get; set; } + public List Transforms { get; } + public Attachment Attachments { get; } + + public FBox Box; + public readonly List Sockets; + public Material[] Materials; + public bool IsTwoSided; + public bool IsProp; + + public int VertexSize => _vertexAttributes.Where(x => x.Enabled).Sum(x => x.Size); + public bool HasVertexColors => _vertexAttributes[(int) EAttribute.Colors].Enabled; + public bool HasSockets => Sockets.Count > 0; + public int TransformsCount => Transforms.Count; + + public bool IsSetup { get; set; } + public bool IsVisible { get; set; } + public bool IsSelected { get; set; } + public bool ShowWireframe { get; set; } + public int SelectedInstance; + + protected UModel() + { + _export = null; + UvCount = 1; + + Box = new FBox(new FVector(-2f), new FVector(2f)); + Sockets = new List(); + Transforms = new List(); + } + + protected UModel(UObject export) + { + _export = export; + Path = _export.GetPathName(); + Name = Path.SubstringAfterLast('/').SubstringBefore('.'); + Type = export.ExportType; + UvCount = 1; + + Box = new FBox(new FVector(-2f), new FVector(2f)); + Sockets = new List(); + Transforms = new List(); + Attachments = new Attachment(Name); + + _vertexAttributes[(int) EAttribute.Index].Enabled = + _vertexAttributes[(int) EAttribute.Normals].Enabled = + _vertexAttributes[(int) EAttribute.Tangent].Enabled = + _vertexAttributes[(int) EAttribute.UVs].Enabled = + _vertexAttributes[(int) EAttribute.Layer].Enabled = true; + } + + protected UModel(UObject export, CBaseMeshLod lod, IReadOnlyList materials, IReadOnlyList vertices, int numLods, Transform transform = null) : this(export) + { + var hasCustomUvs = lod.ExtraUV.IsValueCreated; + UvCount = hasCustomUvs ? Math.Max(lod.NumTexCoords, numLods) : lod.NumTexCoords; + IsTwoSided = lod.IsTwoSided; + + Indices = new uint[lod.Indices.Value.Length]; + for (int i = 0; i < Indices.Length; i++) + { + Indices[i] = (uint) lod.Indices.Value[i]; + } + + Materials = new Material[materials.Count]; + for (int m = 0; m < Materials.Length; m++) + { + if ((materials[m]?.TryLoad(out var material) ?? false) && material is UMaterialInterface unrealMaterial) + Materials[m] = new Material(unrealMaterial); else Materials[m] = new Material(); + } + + _vertexAttributes[(int) EAttribute.Colors].Enabled = lod.VertexColors is { Length: > 0}; + _vertexAttributes[(int) EAttribute.BonesId].Enabled = + _vertexAttributes[(int) EAttribute.BonesWeight].Enabled = vertices is CSkelMeshVertex[]; + + Vertices = new float[lod.NumVerts * VertexSize]; + for (int i = 0; i < vertices.Count; i++) + { + var count = 0; + var baseIndex = i * VertexSize; + var vert = vertices[i]; + Vertices[baseIndex + count++] = i; + Vertices[baseIndex + count++] = vert.Position.X * Constants.SCALE_DOWN_RATIO; + Vertices[baseIndex + count++] = vert.Position.Z * Constants.SCALE_DOWN_RATIO; + Vertices[baseIndex + count++] = vert.Position.Y * Constants.SCALE_DOWN_RATIO; + Vertices[baseIndex + count++] = vert.Normal.X; + Vertices[baseIndex + count++] = vert.Normal.Z; + Vertices[baseIndex + count++] = vert.Normal.Y; + Vertices[baseIndex + count++] = vert.Tangent.X; + Vertices[baseIndex + count++] = vert.Tangent.Z; + Vertices[baseIndex + count++] = vert.Tangent.Y; + Vertices[baseIndex + count++] = vert.UV.U; + Vertices[baseIndex + count++] = vert.UV.V; + Vertices[baseIndex + count++] = hasCustomUvs ? lod.ExtraUV.Value[0][i].U : .5f; + + if (HasVertexColors) + { + var color = lod.VertexColors[i]; + Vertices[baseIndex + count++] = color.R; + Vertices[baseIndex + count++] = color.G; + Vertices[baseIndex + count++] = color.B; + Vertices[baseIndex + count++] = color.A; + } + + if (vert is CSkelMeshVertex skelVert) + { + var weightsHash = skelVert.UnpackWeights(); + Vertices[baseIndex + count++] = skelVert.Bone[0]; + Vertices[baseIndex + count++] = skelVert.Bone[1]; + Vertices[baseIndex + count++] = skelVert.Bone[2]; + Vertices[baseIndex + count++] = skelVert.Bone[3]; + Vertices[baseIndex + count++] = weightsHash[0]; + Vertices[baseIndex + count++] = weightsHash[1]; + Vertices[baseIndex + count++] = weightsHash[2]; + Vertices[baseIndex + count++] = weightsHash[3]; + } + } + + Sections = new Section[lod.Sections.Value.Length]; + for (var s = 0; s < Sections.Length; s++) + { + var section = lod.Sections.Value[s]; + Sections[s] = new Section(section.MaterialIndex, section.NumFaces * 3, section.FirstIndex); + if (section.IsValid) Sections[s].SetupMaterial(Materials[section.MaterialIndex]); + } + + AddInstance(transform ?? Transform.Identity); + } + + public virtual void Setup(Options options) + { + Handle = GL.CreateProgram(); + Ebo = new BufferObject(Indices, BufferTarget.ElementArrayBuffer); + Vbo = new BufferObject(Vertices, BufferTarget.ArrayBuffer); + Vao = new VertexArrayObject(Vbo, Ebo); + + var offset = 0; + var broken = GL.GetInteger(GetPName.MaxTextureCoords) == 0; + for (int i = 0; i < _vertexAttributes.Count; i++) + { + var attribute = _vertexAttributes[i]; + if (!attribute.Enabled) continue; + + if (i != 5 || !broken) + { + Vao.VertexAttributePointer((uint) i, attribute.Size, i == 0 ? VertexAttribPointerType.Int : VertexAttribPointerType.Float, VertexSize, offset); + } + offset += attribute.Size; + } + + SetupInstances(); // instanced models transform + + // setup all used materials for use in different UV channels + for (var i = 0; i < Materials.Length; i++) + { + if (!Materials[i].IsUsed) continue; + Materials[i].Setup(options, broken ? 1 : UvCount); + } + + if (options.Models.Count == 1 && Sections.All(x => !x.Show)) + { + IsVisible = true; + foreach (var section in Sections) + { + section.Show = true; + } + } + else foreach (var section in Sections) + { + if (!IsVisible) IsVisible = section.Show; + } + + IsSetup = true; + } + + public virtual void Render(Shader shader, bool outline = false) + { + if (outline) GL.Disable(EnableCap.DepthTest); + if (IsTwoSided) GL.Disable(EnableCap.CullFace); + if (IsSelected) + { + GL.Enable(EnableCap.StencilTest); + GL.StencilFunc(outline ? StencilFunction.Notequal : StencilFunction.Always, 1, 0xFF); + } + + if (this is SkeletalModel skeletalModel) skeletalModel.Render(shader); + else shader.SetUniform("uIsAnimated", false); + if (!outline) + { + shader.SetUniform("uUvCount", UvCount); + shader.SetUniform("uHasVertexColors", HasVertexColors); + } + + Vao.Bind(); + GL.PolygonMode(MaterialFace.FrontAndBack, ShowWireframe ? PolygonMode.Line : PolygonMode.Fill); + foreach (var section in Sections) + { + if (!section.Show) continue; + if (!outline) + { + shader.SetUniform("uSectionColor", section.Color); + Materials[section.MaterialIndex].Render(shader); + } + + GL.DrawElementsInstanced(PrimitiveType.Triangles, section.FacesCount, DrawElementsType.UnsignedInt, section.FirstFaceIndexPtr, TransformsCount); + } + GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill); + Vao.Unbind(); + + if (IsSelected) + { + GL.StencilFunc(StencilFunction.Always, 0, 0xFF); + GL.Disable(EnableCap.StencilTest); + } + if (IsTwoSided) GL.Enable(EnableCap.CullFace); + if (outline) GL.Enable(EnableCap.DepthTest); + } + + public void PickingRender(Shader shader) + { + if (IsTwoSided) GL.Disable(EnableCap.CullFace); + if (this is SkeletalModel skeletalModel) + skeletalModel.Render(shader); + else shader.SetUniform("uIsAnimated", false); + + Vao.Bind(); + foreach (var section in Sections) + { + if (!section.Show) continue; + GL.DrawElementsInstanced(PrimitiveType.Triangles, section.FacesCount, DrawElementsType.UnsignedInt, section.FirstFaceIndexPtr, TransformsCount); + } + Vao.Unbind(); + + if (IsTwoSided) GL.Enable(EnableCap.CullFace); + } + + public void Update(Options options) + { + MatrixVbo.Bind(); + for (int instance = 0; instance < TransformsCount; instance++) + { + MatrixVbo.Update(instance, Transforms[instance].Matrix); + } + MatrixVbo.Unbind(); + + var worldMatrix = GetTransform().Matrix; + foreach (var socket in Sockets) + { + if (!socket.IsDaron) continue; + + var boneMatrix = Matrix4x4.Identity; + if (this is SkeletalModel skeletalModel && skeletalModel.Skeleton.BonesByLoweredName.TryGetValue(socket.BoneName.Text.ToLower(), out var bone)) + boneMatrix = skeletalModel.Skeleton.GetBoneMatrix(bone); + + var socketRelation = boneMatrix * worldMatrix; + foreach (var info in socket.AttachedModels) + { + if (!options.TryGetModel(info.Guid, out var attachedModel)) + continue; + + attachedModel.Transforms[info.Instance].Relation = socket.Transform.LocalMatrix * socketRelation; + attachedModel.Update(options); + } + } + } + + public void AddInstance(Transform transform) + { + SelectedInstance = TransformsCount; + Transforms.Add(transform); + } + + public void SetupInstances() + { + MatrixVbo = new BufferObject(TransformsCount, BufferTarget.ArrayBuffer); + for (int instance = 0; instance < TransformsCount; instance++) + { + MatrixVbo.Update(instance, Transforms[instance].Matrix); + } + Vao.BindInstancing(); // VertexAttributePointer + } + + public Transform GetTransform() => Transforms[SelectedInstance]; + public Matrix4x4 GetSocketTransform(int index) + { + var socket = Sockets[index]; + var worldMatrix = GetTransform().Matrix; + var boneMatrix = Matrix4x4.Identity; + if (this is SkeletalModel skeletalModel && skeletalModel.Skeleton.BonesByLoweredName.TryGetValue(socket.BoneName.Text.ToLower(), out var bone)) + boneMatrix = skeletalModel.Skeleton.GetBoneMatrix(bone); + + var socketRelation = boneMatrix * worldMatrix; + return socket.Transform.LocalMatrix * socketRelation; + } + + public bool Save(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.CurrentDir.TexturePlatform, + ExportMorphTargets = UserSettings.Default.SaveMorphTargets + }; + var toSave = new Exporter(_export, exportOptions); + return toSave.TryWriteToDir(new DirectoryInfo(UserSettings.Default.ModelDirectory), out label, out savedFilePath); + } + + public virtual void Dispose() + { + Ebo?.Dispose(); + Vbo?.Dispose(); + MatrixVbo?.Dispose(); + Vao?.Dispose(); + foreach (var socket in Sockets) + { + socket?.Dispose(); + } + Sockets.Clear(); + + GL.DeleteProgram(Handle); + } +} diff --git a/FModel/Views/Snooper/Options.cs b/FModel/Views/Snooper/Options.cs index 6194b175..83d37d33 100644 --- a/FModel/Views/Snooper/Options.cs +++ b/FModel/Views/Snooper/Options.cs @@ -1,10 +1,7 @@ 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; @@ -22,7 +19,7 @@ public class Options public int SelectedMorph { get; private set; } public int SelectedAnimation{ get; private set; } - public readonly Dictionary Models; + public readonly Dictionary Models; public readonly Dictionary Textures; public readonly List Lights; @@ -31,12 +28,11 @@ public class Options public readonly Dictionary Icons; - private readonly ETexturePlatform _platform; private readonly string _game; public Options() { - Models = new Dictionary(); + Models = new Dictionary(); Textures = new Dictionary(); Lights = new List(); @@ -60,7 +56,6 @@ public class Options ["tl_next"] = new ("tl_next"), }; - _platform = UserSettings.Default.CurrentDir.TexturePlatform; _game = Services.ApplicationService.ApplicationView.CUE4Parse.Provider.InternalGameName.ToUpper(); SelectModel(Guid.Empty); @@ -107,27 +102,30 @@ public class Options public void RemoveModel(FGuid guid) { - if (!TryGetModel(guid, out var model)) return; + if (!TryGetModel(guid, out var m) || m is not UModel model) + return; DetachAndRemoveModels(model, true); model.Dispose(); Models.Remove(guid); } - private void DetachAndRemoveModels(Model model, bool detach) + private void DetachAndRemoveModels(UModel model, bool detach) { foreach (var socket in model.Sockets.ToList()) { foreach (var info in socket.AttachedModels) { - if (!TryGetModel(info.Guid, out var attachedModel)) continue; + if (!TryGetModel(info.Guid, out var m) || m is not UModel attachedModel) + continue; - if (attachedModel.IsAnimatedProp) + var t = attachedModel.GetTransform(); + if (attachedModel.IsProp) { - attachedModel.SafeDetachModel(model); + attachedModel.Attachments.SafeDetach(model, t); RemoveModel(info.Guid); } - else if (detach) attachedModel.SafeDetachModel(model); + else if (detach) attachedModel.Attachments.SafeDetach(model, t); } if (socket.IsVirtual) @@ -152,17 +150,20 @@ public class Options { foreach (var guid in animation.AttachedModels) { - if (!TryGetModel(guid, out var animatedModel)) continue; + if (!TryGetModel(guid, out var model) || model is not SkeletalModel animatedModel) + continue; animatedModel.Skeleton.ResetAnimatedData(true); DetachAndRemoveModels(animatedModel, false); } + animation.Dispose(); } - foreach (var kvp in Models.ToList().Where(kvp => kvp.Value.IsAnimatedProp)) - { - RemoveModel(kvp.Key); - } + + foreach (var kvp in Models) + if (kvp.Value.IsProp) + RemoveModel(kvp.Key); + Animations.Clear(); } @@ -171,7 +172,7 @@ public class Options SelectedSection = index; } - public void SelectMorph(int index, Model model) + public void SelectMorph(int index, SkeletalModel model) { SelectedMorph = index; model.UpdateMorph(SelectedMorph); @@ -191,8 +192,8 @@ public class Options return texture != null; } - public bool TryGetModel(out Model model) => Models.TryGetValue(SelectedModel, out model); - public bool TryGetModel(FGuid guid, out Model model) => Models.TryGetValue(guid, out model); + public bool TryGetModel(out UModel model) => Models.TryGetValue(SelectedModel, out model); + public bool TryGetModel(FGuid guid, out UModel model) => Models.TryGetValue(guid, out model); public bool TryGetSection(out Section section) => TryGetSection(SelectedModel, out section); public bool TryGetSection(FGuid guid, out Section section) @@ -205,7 +206,7 @@ public class Options section = null; return false; } - public bool TryGetSection(Model model, out Section section) + public bool TryGetSection(UModel model, out Section section) { if (SelectedSection >= 0 && SelectedSection < model.Sections.Length) section = model.Sections[SelectedSection]; else section = null; @@ -222,22 +223,6 @@ 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 = _platform, - 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 156aff71..54ec487f 100644 --- a/FModel/Views/Snooper/Renderer.cs +++ b/FModel/Views/Snooper/Renderer.cs @@ -25,6 +25,7 @@ using FModel.Views.Snooper.Lights; using FModel.Views.Snooper.Models; using FModel.Views.Snooper.Shading; using OpenTK.Windowing.GraphicsLibraryFramework; +using UModel = FModel.Views.Snooper.Models.UModel; namespace FModel.Views.Snooper; @@ -91,6 +92,7 @@ public class Renderer : IDisposable LoadWorld(cancellationToken, wd, Transform.Identity); break; } + SetupCamera(); } public void Swap(UMaterialInstance unrealMaterial) @@ -104,7 +106,7 @@ public class Renderer : IDisposable public void Animate(UObject anim) => Animate(anim, Options.SelectedModel); private void Animate(UObject anim, FGuid guid) { - if (!Options.TryGetModel(guid, out var model) || !model.HasSkeleton) + if (!Options.TryGetModel(guid, out var m) || m is not SkeletalModel model) return; float maxElapsedTime; @@ -141,32 +143,39 @@ public class Renderer : IDisposable t.Scale = offset.Scale3D; } + UModel addedModel = null; switch (export) { case UStaticMesh st: { guid = st.LightingGuid; - if (Options.TryGetModel(guid, out var instancedModel)) - instancedModel.AddInstance(t); + if (Options.TryGetModel(guid, out addedModel)) + { + addedModel.AddInstance(t); + } else if (st.TryConvert(out var mesh)) - Options.Models[guid] = new Model(st, mesh, t); + { + addedModel = new StaticModel(st, mesh, t); + Options.Models[guid] = addedModel; + } break; } case USkeletalMesh sk: { guid = Guid.NewGuid(); if (!Options.Models.ContainsKey(guid) && sk.TryConvert(out var mesh)) - Options.Models[guid] = new Model(sk, mesh, t); + { + addedModel = new SkeletalModel(sk, mesh, t); + Options.Models[guid] = addedModel; + } break; } - default: - throw new ArgumentException(); } - if (!Options.TryGetModel(guid, out var addedModel)) - continue; + if (addedModel == null) + throw new ArgumentException("Unknown model type"); - addedModel.IsAnimatedProp = true; + addedModel.IsProp = true; if (notifyClass.TryGetValue(out UObject skeletalMeshPropAnimation, "SkeletalMeshPropAnimation", "Animation")) Animate(skeletalMeshPropAnimation, guid); if (notifyClass.TryGetValue(out FName socketName, "SocketName")) @@ -181,7 +190,8 @@ public class Renderer : IDisposable 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 }); + addedModel.Attachments.Attach(model, addedModel.GetTransform(), s, + new SocketAttachementInfo { Guid = guid, Instance = addedModel.SelectedInstance }); } } break; @@ -231,7 +241,7 @@ public class Renderer : IDisposable // render model pass foreach (var model in Options.Models.Values) { - if (!model.Show) continue; + if (!model.IsVisible) continue; model.Render(_shader); } @@ -249,7 +259,7 @@ public class Renderer : IDisposable } // outline pass - if (Options.TryGetModel(out var selected) && selected.Show) + if (Options.TryGetModel(out var selected) && selected.IsVisible) { _outline.Render(viewMatrix, CameraOp.Position, projMatrix); selected.Render(_outline, true); @@ -265,22 +275,23 @@ public class Renderer : IDisposable foreach (var animation in Options.Animations) { animation.TimeCalculation(Options.Tracker.ElapsedTime); - foreach (var guid in animation.AttachedModels.Where(guid => Options.Models[guid].HasSkeleton)) + foreach (var guid in animation.AttachedModels) { - Options.Models[guid].Skeleton.UpdateAnimationMatrices(animation, AnimateWithRotationOnly); + if (Options.Models[guid] is not SkeletalModel skeletalModel) continue; + skeletalModel.Skeleton.UpdateAnimationMatrices(animation, AnimateWithRotationOnly); } } foreach (var model in Options.Models.Values) { - model.UpdateMatrices(Options); + model.Update(Options); } CameraOp.Modify(wnd.KeyboardState, deltaSeconds); if (wnd.KeyboardState.IsKeyPressed(Keys.Z) && Options.TryGetModel(out var selectedModel) && - selectedModel.HasSkeleton) + selectedModel is SkeletalModel) { Options.RemoveAnimations(); Options.AnimateMesh(true); @@ -309,9 +320,8 @@ public class Renderer : IDisposable if (!original.TryConvert(out var mesh)) return; - Options.Models[guid] = new Model(original, mesh); + Options.Models[guid] = new StaticModel(original, mesh); Options.SelectModel(guid); - SetupCamera(Options.Models[guid].Box); } private void LoadSkeletalMesh(USkeletalMesh original) @@ -319,14 +329,14 @@ 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, mesh); + var skeletalModel = new SkeletalModel(original, mesh); + Options.Models[guid] = skeletalModel; Options.SelectModel(guid); - SetupCamera(Options.Models[guid].Box); } private void LoadMaterialInstance(UMaterialInstance original) { - if (!Utils.TryLoadObject("Engine/Content/EditorMeshes/EditorCube.EditorCube", out UStaticMesh editorCube)) + if (!Utils.TryLoadObject("Engine/Content/BasicShapes/Cube.Cube", out UStaticMesh editorCube)) return; var guid = editorCube.LightingGuid; @@ -340,12 +350,15 @@ public class Renderer : IDisposable if (!editorCube.TryConvert(out var mesh)) return; - Options.Models[guid] = new Cube(mesh, original); + Options.Models[guid] = new StaticModel(original, mesh); Options.SelectModel(guid); - SetupCamera(Options.Models[guid].Box); } - private void SetupCamera(FBox box) => CameraOp.Setup(box); + private void SetupCamera() + { + if (Options.TryGetModel(out var model)) + CameraOp.Setup(model.Box); + } private void LoadWorld(CancellationToken cancellationToken, UWorld original, Transform transform) { @@ -432,8 +445,8 @@ public class Renderer : IDisposable } else if (m.TryConvert(out var mesh)) { - model = new Model(m, mesh, t); - model.TwoSided = actor.GetOrDefault("bMirrored", staticMeshComp.GetOrDefault("bDisallowMeshPaintPerInstance", model.TwoSided)); + model = new StaticModel(m, mesh, t); + model.IsTwoSided = actor.GetOrDefault("bMirrored", staticMeshComp.GetOrDefault("bDisallowMeshPaintPerInstance", model.IsTwoSided)); if (actor.TryGetValue(out FPackageIndex baseMaterial, "BaseMaterial") && actor.TryGetAllValues(out FPackageIndex[] textureData, "TextureData")) diff --git a/FModel/Views/Snooper/Shading/Material.cs b/FModel/Views/Snooper/Shading/Material.cs index 7fd0ca9d..c9f5b38b 100644 --- a/FModel/Views/Snooper/Shading/Material.cs +++ b/FModel/Views/Snooper/Shading/Material.cs @@ -316,7 +316,7 @@ public class Material : IDisposable } } - public bool ImGuiTextures(Dictionary icons, Model model) + public bool ImGuiTextures(Dictionary icons, UModel model) { if (ImGui.BeginTable("material_textures", 2)) { diff --git a/FModel/Views/Snooper/SnimGui.cs b/FModel/Views/Snooper/SnimGui.cs index 770f1a1f..bec7e589 100644 --- a/FModel/Views/Snooper/SnimGui.cs +++ b/FModel/Views/Snooper/SnimGui.cs @@ -184,7 +184,7 @@ public class SnimGui { foreach (var model in s.Renderer.Options.Models.Values) { - b |= s.Renderer.Options.TrySave(model.Export, out _, out _); + b |= model.Save(out _, out _); } } }); @@ -403,16 +403,16 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio ImGui.TableHeadersRow(); var i = 0; - foreach ((FGuid guid, Model model) in s.Renderer.Options.Models) + foreach ((var guid, var model) in s.Renderer.Options.Models) { ImGui.PushID(i); ImGui.TableNextRow(); ImGui.TableNextColumn(); - if (!model.Show) + if (!model.IsVisible) ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, ImGui.GetColorU32(new Vector4(1, 0, 0, .5f))); - else if (model.IsAttachment) + else if (model.Attachments.IsAttachment) ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, ImGui.GetColorU32(new Vector4(0, .75f, 0, .5f))); - else if (model.IsAttached) + else if (model.Attachments.IsAttached) ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, ImGui.GetColorU32(new Vector4(1, 1, 0, .5f))); ImGui.Text(model.TransformsCount.ToString("D")); @@ -426,16 +426,16 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio Popup(() => { s.Renderer.Options.SelectModel(guid); - if (ImGui.MenuItem("Show", null, model.Show)) model.Show = !model.Show; - if (ImGui.MenuItem("Wireframe", null, model.Wireframe)) model.Wireframe = !model.Wireframe; + if (ImGui.MenuItem("Show", null, model.IsVisible)) model.IsVisible = !model.IsVisible; + if (ImGui.MenuItem("Wireframe", null, model.ShowWireframe)) model.ShowWireframe = !model.ShowWireframe; ImGui.Separator(); if (ImGui.MenuItem("Save")) { s.WindowShouldFreeze(true); - _saver.Value = s.Renderer.Options.TrySave(model.Export, out _saver.Label, out _saver.Path); + _saver.Value = model.Save(out _saver.Label, out _saver.Path); s.WindowShouldFreeze(false); } - if (ImGui.MenuItem("Animate", model.HasSkeleton)) + if (ImGui.MenuItem("Animate", model is SkeletalModel)) { if (_swapper.IsAware) { @@ -455,10 +455,10 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio _swapper.Value = true; } } - if (ImGui.MenuItem("Bone Hierarchy", model.HasSkeleton)) + if (ImGui.MenuItem("Skeleton Tree", model is SkeletalModel)) { _bh_open = true; - ImGui.SetWindowFocus("Bone Hierarchy"); + ImGui.SetWindowFocus("Skeleton Tree"); } if (ImGui.MenuItem("Teleport To")) { @@ -473,8 +473,8 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio }); ImGui.TableNextColumn(); - ImGui.Image(s.Renderer.Options.Icons[model.AttachIcon].GetPointer(), new Vector2(_tableWidth)); - TooltipCopy(model.AttachTooltip); + ImGui.Image(s.Renderer.Options.Icons[model.Attachments.Icon].GetPointer(), new Vector2(_tableWidth)); + TooltipCopy(model.Attachments.Tooltip); ImGui.PopID(); i++; @@ -501,14 +501,14 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio { var isAttached = socket.AttachedModels.Contains(info); ImGui.PushID(i); - ImGui.BeginDisabled(selectedModel.IsAttached && !isAttached); + ImGui.BeginDisabled(selectedModel.Attachments.IsAttached && !isAttached); switch (isAttached) { case false when ImGui.Button($"Attach to '{socket.Name}'"): - selectedModel.AttachModel(model, socket, info); + selectedModel.Attachments.Attach(model, selectedModel.GetTransform(), socket, info); break; case true when ImGui.Button($"Detach from '{socket.Name}'"): - selectedModel.DetachModel(model, socket, info); + selectedModel.Attachments.Detach(model, selectedModel.GetTransform(), socket, info); break; } ImGui.EndDisabled(); @@ -532,14 +532,14 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio { Layout("Entity");ImGui.Text($" : ({model.Type}) {model.Name}"); Layout("Guid");ImGui.Text($" : {s.Renderer.Options.SelectedModel.ToString(EGuidFormats.UniqueObjectGuid)}"); - if (model.HasSkeleton) + if (model is SkeletalModel skeletalModel) { - Layout("Skeleton");ImGui.Text($" : {model.Skeleton.Name}"); - Layout("Bones");ImGui.Text($" : x{model.Skeleton.BoneCount}"); + Layout("Skeleton");ImGui.Text($" : {skeletalModel.Skeleton.Name}"); + Layout("Bones");ImGui.Text($" : x{skeletalModel.Skeleton.BoneCount}"); } else { - Layout("Two Sided");ImGui.Text($" : {model.TwoSided}"); + Layout("Two Sided");ImGui.Text($" : {model.IsTwoSided}"); } Layout("Sockets");ImGui.Text($" : x{model.Sockets.Count}"); @@ -620,7 +620,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio if (ImGui.BeginTabItem("Morph Targets")) { - if (model.HasMorphTargets) + if (model is SkeletalModel { HasMorphTargets: true } skeletalModel) { const float width = 10; var region = ImGui.GetContentRegionAvail(); @@ -628,12 +628,12 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio if (ImGui.BeginListBox("", box)) { - for (int i = 0; i < model.Morphs.Count; i++) + for (int i = 0; i < skeletalModel.Morphs.Count; i++) { ImGui.PushID(i); - if (ImGui.Selectable(model.Morphs[i].Name, s.Renderer.Options.SelectedMorph == i)) + if (ImGui.Selectable(skeletalModel.Morphs[i].Name, s.Renderer.Options.SelectedMorph == i)) { - s.Renderer.Options.SelectMorph(i, model); + s.Renderer.Options.SelectMorph(i, skeletalModel); } ImGui.PopID(); } @@ -641,10 +641,10 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(2f, 0f)); ImGui.SameLine(); ImGui.PushID(99); - ImGui.VSliderFloat("", box with { X = width }, ref model.MorphTime, 0.0f, 1.0f, "", ImGuiSliderFlags.AlwaysClamp); + ImGui.VSliderFloat("", box with { X = width }, ref skeletalModel.MorphTime, 0.0f, 1.0f, "", ImGuiSliderFlags.AlwaysClamp); ImGui.PopID(); ImGui.PopStyleVar(); ImGui.Spacing(); - ImGui.Text($"Time: {model.MorphTime:P}%"); + ImGui.Text($"Time: {skeletalModel.MorphTime:P}%"); } } else CenteredTextColored(_errorColor, "Selected Mesh Has No Morph Targets"); @@ -655,7 +655,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio ImGui.PopStyleVar(); } - private void DrawMaterialInspector(Dictionary icons, Model model, Section section) + private void DrawMaterialInspector(Dictionary icons, UModel model, Section section) { var material = model.Materials[section.MaterialIndex]; @@ -724,14 +724,24 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio private void DrawBoneHierarchy(Snooper s) { ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero); - if (ImGui.Begin("Bone Hierarchy", ref _bh_open, ImGuiWindowFlags.NoScrollbar) && s.Renderer.Options.TryGetModel(out var model)) + if (ImGui.Begin("Skeleton Tree", ref _bh_open, ImGuiWindowFlags.NoScrollbar) && s.Renderer.Options.TryGetModel(out var model) && model is SkeletalModel skeletalModel) { - model.Skeleton.ImGuiBoneBreadcrumb(); - if (ImGui.BeginTable("bone_hierarchy", 2, ImGuiTableFlags.NoSavedSettings | ImGuiTableFlags.RowBg, ImGui.GetContentRegionAvail(), ImGui.GetWindowWidth())) + // ImGui.SeparatorText("Options"); + // if (ImGui.BeginTable("skeleton_options", 2)) + // { + // Layout("Draw All Bones");ImGui.PushID(1); + // ImGui.Checkbox("", ref skeletalModel.Skeleton.DrawAllBones); + // ImGui.PopID(); + // + // ImGui.EndTable(); + // } + // ImGui.SeparatorText("Tree"); + skeletalModel.Skeleton.ImGuiBoneBreadcrumb(); + if (ImGui.BeginTable("skeleton_tree", 2, ImGuiTableFlags.NoSavedSettings | ImGuiTableFlags.RowBg, ImGui.GetContentRegionAvail(), ImGui.GetWindowWidth())) { ImGui.TableSetupColumn("Bone", ImGuiTableColumnFlags.WidthStretch); ImGui.TableSetupColumn("", ImGuiTableColumnFlags.NoHeaderWidth | ImGuiTableColumnFlags.WidthFixed, _tableWidth); - model.Skeleton.ImGuiBoneHierarchy(); + skeletalModel.Skeleton.ImGuiBoneHierarchy(); ImGui.EndTable(); } } @@ -828,7 +838,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio } } - private void MeshWindow(string name, Renderer renderer, Action, Model> content, bool styled = true) + private void MeshWindow(string name, Renderer renderer, Action, UModel> content, bool styled = true) { Window(name, () => { @@ -837,7 +847,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio }, styled); } - private void SectionWindow(string name, Renderer renderer, Action, Model, Section> content, bool styled = true) + private void SectionWindow(string name, Renderer renderer, Action, UModel, Section> content, bool styled = true) { MeshWindow(name, renderer, (icons, model) => {