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) =>
{