mirror of
https://github.com/4sval/FModel.git
synced 2026-04-26 00:04:53 -05:00
skeleton tree + fixed skeleton bones incorrect relation
This commit is contained in:
parent
1aa45b1b88
commit
841f40e32b
|
|
@ -1 +1 @@
|
||||||
Subproject commit d0c44876fc718e0893ed699551bc354b8d2a5422
|
Subproject commit e65380d4c735ea6222fe53ba8ff1da78aa2329d7
|
||||||
|
|
@ -107,6 +107,8 @@
|
||||||
<None Remove="Resources\picking.vert" />
|
<None Remove="Resources\picking.vert" />
|
||||||
<None Remove="Resources\light.frag" />
|
<None Remove="Resources\light.frag" />
|
||||||
<None Remove="Resources\light.vert" />
|
<None Remove="Resources\light.vert" />
|
||||||
|
<None Remove="Resources\bone.frag" />
|
||||||
|
<None Remove="Resources\bone.vert" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
@ -130,6 +132,8 @@
|
||||||
<EmbeddedResource Include="Resources\picking.vert" />
|
<EmbeddedResource Include="Resources\picking.vert" />
|
||||||
<EmbeddedResource Include="Resources\light.frag" />
|
<EmbeddedResource Include="Resources\light.frag" />
|
||||||
<EmbeddedResource Include="Resources\light.vert" />
|
<EmbeddedResource Include="Resources\light.vert" />
|
||||||
|
<EmbeddedResource Include="Resources\bone.frag" />
|
||||||
|
<EmbeddedResource Include="Resources\bone.vert" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
10
FModel/Resources/bone.frag
Normal file
10
FModel/Resources/bone.frag
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
23
FModel/Resources/bone.vert
Normal file
23
FModel/Resources/bone.vert
Normal file
|
|
@ -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));
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using CUE4Parse_Conversion;
|
||||||
using CUE4Parse_Conversion.Animations.PSA;
|
using CUE4Parse_Conversion.Animations.PSA;
|
||||||
using CUE4Parse.UE4.Assets.Exports;
|
using CUE4Parse.UE4.Assets.Exports;
|
||||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||||
|
using FModel.Settings;
|
||||||
|
using FModel.Views.Snooper.Models;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
|
||||||
namespace FModel.Views.Snooper.Animations;
|
namespace FModel.Views.Snooper.Animations;
|
||||||
|
|
@ -118,11 +122,11 @@ public class Animation : IDisposable
|
||||||
foreach ((var guid, var model) in s.Renderer.Options.Models)
|
foreach ((var guid, var model) in s.Renderer.Options.Models)
|
||||||
{
|
{
|
||||||
var selected = AttachedModels.Contains(guid);
|
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);
|
if (selected) AttachedModels.Remove(guid); else AttachedModels.Add(guid);
|
||||||
model.Skeleton.ResetAnimatedData(true);
|
skeletalModel.Skeleton.ResetAnimatedData(true);
|
||||||
if (!selected) model.Skeleton.Animate(UnrealAnim);
|
if (!selected) skeletalModel.Skeleton.Animate(UnrealAnim);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui.EndMenu();
|
ImGui.EndMenu();
|
||||||
|
|
@ -130,7 +134,7 @@ public class Animation : IDisposable
|
||||||
if (ImGui.MenuItem("Save"))
|
if (ImGui.MenuItem("Save"))
|
||||||
{
|
{
|
||||||
s.WindowShouldFreeze(true);
|
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);
|
s.WindowShouldFreeze(false);
|
||||||
}
|
}
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ namespace FModel.Views.Snooper.Animations;
|
||||||
public class Bone
|
public class Bone
|
||||||
{
|
{
|
||||||
public readonly int Index;
|
public readonly int Index;
|
||||||
public readonly int ParentIndex;
|
public int ParentIndex;
|
||||||
public readonly Transform Rest;
|
public readonly Transform Rest;
|
||||||
public readonly bool IsVirtual;
|
public readonly bool IsVirtual;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ public class Skeleton : IDisposable
|
||||||
private int TotalBoneCount => BoneCount + _additionalBoneCount;
|
private int TotalBoneCount => BoneCount + _additionalBoneCount;
|
||||||
|
|
||||||
public bool IsAnimated { get; private set; }
|
public bool IsAnimated { get; private set; }
|
||||||
|
public bool DrawAllBones;
|
||||||
public string SelectedBone;
|
public string SelectedBone;
|
||||||
|
|
||||||
public Skeleton()
|
public Skeleton()
|
||||||
|
|
@ -62,6 +63,7 @@ public class Skeleton : IDisposable
|
||||||
if (boneIndex == 0) SelectedBone = boneName;
|
if (boneIndex == 0) SelectedBone = boneName;
|
||||||
BonesByLoweredName[boneName] = bone;
|
BonesByLoweredName[boneName] = bone;
|
||||||
}
|
}
|
||||||
|
_breadcrumb.Add(SelectedBone);
|
||||||
_boneMatriceAtFrame = new Matrix4x4[BoneCount];
|
_boneMatriceAtFrame = new Matrix4x4[BoneCount];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74,7 +76,7 @@ public class Skeleton : IDisposable
|
||||||
|
|
||||||
if (!BonesByLoweredName.TryGetValue(boneName, out var bone))
|
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,
|
Rotation = referenceSkeleton.FinalRefBonePose[boneIndex].Rotation,
|
||||||
Position = referenceSkeleton.FinalRefBonePose[boneIndex].Translation * Constants.SCALE_DOWN_RATIO,
|
Position = referenceSkeleton.FinalRefBonePose[boneIndex].Translation * Constants.SCALE_DOWN_RATIO,
|
||||||
|
|
@ -83,9 +85,10 @@ public class Skeleton : IDisposable
|
||||||
|
|
||||||
if (!bone.IsRoot)
|
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];
|
var parentBone = BonesByLoweredName[bone.LoweredParentName];
|
||||||
|
|
||||||
|
bone.ParentIndex = parentBone.Index;
|
||||||
bone.Rest.Relation = parentBone.Rest.Matrix;
|
bone.Rest.Relation = parentBone.Rest.Matrix;
|
||||||
parentBone.LoweredChildNames.Add(boneName);
|
parentBone.LoweredChildNames.Add(boneName);
|
||||||
}
|
}
|
||||||
|
|
@ -241,21 +244,37 @@ public class Skeleton : IDisposable
|
||||||
|
|
||||||
public void ImGuiBoneBreadcrumb()
|
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];
|
var boneName = _breadcrumb[i];
|
||||||
ImGui.SameLine();
|
var size = ImGui.CalcTextSize(boneName);
|
||||||
var clicked = ImGui.SmallButton(boneName);
|
var position = new Vector2(x + 5, y - size.Y / 2f);
|
||||||
ImGui.SameLine();
|
|
||||||
ImGui.Text(">");
|
|
||||||
|
|
||||||
if (clicked)
|
ImGui.SetCursorScreenPos(position);
|
||||||
|
if (ImGui.InvisibleButton($"breakfast_{boneName}", size, ImGuiButtonFlags.MouseButtonLeft))
|
||||||
{
|
{
|
||||||
SelectedBone = boneName;
|
SelectedBone = boneName;
|
||||||
_breadcrumb.RemoveRange(0, i + 1);
|
_breadcrumb.RemoveRange(0, i);
|
||||||
break;
|
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()
|
public void ImGuiBoneHierarchy()
|
||||||
|
|
@ -276,7 +295,7 @@ public class Skeleton : IDisposable
|
||||||
|
|
||||||
ImGui.SetNextItemOpen(bone.LoweredChildNames.Count <= 1 || flags.HasFlag(ImGuiTreeNodeFlags.Selected), ImGuiCond.Appearing);
|
ImGui.SetNextItemOpen(bone.LoweredChildNames.Count <= 1 || flags.HasFlag(ImGuiTreeNodeFlags.Selected), ImGuiCond.Appearing);
|
||||||
var open = ImGui.TreeNodeEx(boneName, flags);
|
var open = ImGui.TreeNodeEx(boneName, flags);
|
||||||
if (ImGui.IsItemClicked() && !ImGui.IsItemToggledOpen())
|
if (ImGui.IsItemClicked() && !ImGui.IsItemToggledOpen() && bone.IsDaron)
|
||||||
{
|
{
|
||||||
SelectedBone = boneName;
|
SelectedBone = boneName;
|
||||||
_breadcrumb.Clear();
|
_breadcrumb.Clear();
|
||||||
|
|
|
||||||
|
|
@ -54,20 +54,20 @@ public class PickingTexture : IDisposable
|
||||||
Bind(0);
|
Bind(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Render(Matrix4x4 viewMatrix, Matrix4x4 projMatrix, IDictionary<FGuid,Model> models)
|
public void Render(Matrix4x4 viewMatrix, Matrix4x4 projMatrix, IDictionary<FGuid, UModel> models)
|
||||||
{
|
{
|
||||||
Bind();
|
Bind();
|
||||||
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
|
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
|
||||||
|
|
||||||
_shader.Render(viewMatrix, projMatrix);
|
_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("uA", guid.A);
|
||||||
_shader.SetUniform("uB", guid.B);
|
_shader.SetUniform("uB", guid.B);
|
||||||
_shader.SetUniform("uC", guid.C);
|
_shader.SetUniform("uC", guid.C);
|
||||||
_shader.SetUniform("uD", guid.D);
|
_shader.SetUniform("uD", guid.D);
|
||||||
|
|
||||||
if (!model.Show) continue;
|
if (!model.IsVisible) continue;
|
||||||
model.PickingRender(_shader);
|
model.PickingRender(_shader);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
57
FModel/Views/Snooper/Models/Attachment.cs
Normal file
57
FModel/Views/Snooper/Models/Attachment.cs
Normal file
|
|
@ -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<string> _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<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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}'");
|
||||||
|
}
|
||||||
52
FModel/Views/Snooper/Models/BoneModel.cs
Normal file
52
FModel/Views/Snooper/Models/BoneModel.cs
Normal file
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
14
FModel/Views/Snooper/Models/EAttribute.cs
Normal file
14
FModel/Views/Snooper/Models/EAttribute.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
namespace FModel.Views.Snooper.Models;
|
||||||
|
|
||||||
|
public enum EAttribute
|
||||||
|
{
|
||||||
|
Index,
|
||||||
|
Position,
|
||||||
|
Normals,
|
||||||
|
Tangent,
|
||||||
|
UVs,
|
||||||
|
Layer,
|
||||||
|
Colors,
|
||||||
|
BonesId,
|
||||||
|
BonesWeight
|
||||||
|
}
|
||||||
38
FModel/Views/Snooper/Models/IRenderableModel.cs
Normal file
38
FModel/Views/Snooper/Models/IRenderableModel.cs
Normal file
|
|
@ -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<uint> Ebo { get; set; }
|
||||||
|
protected BufferObject<float> Vbo { get; set; }
|
||||||
|
protected BufferObject<Matrix4x4> MatrixVbo { get; set; }
|
||||||
|
protected VertexArrayObject<float, uint> 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<Transform> 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);
|
||||||
|
}
|
||||||
|
|
@ -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<uint> _ebo;
|
|
||||||
private BufferObject<float> _vbo;
|
|
||||||
private BufferObject<float> _morphVbo;
|
|
||||||
private BufferObject<Matrix4x4> _matrixVbo;
|
|
||||||
private VertexArrayObject<float, uint> _vao;
|
|
||||||
|
|
||||||
private readonly List<VertexAttribute> _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<Socket> Sockets;
|
|
||||||
|
|
||||||
public bool HasMorphTargets => Morphs.Count > 0;
|
|
||||||
public readonly List<Morph> Morphs;
|
|
||||||
|
|
||||||
private string _attachedTo = string.Empty;
|
|
||||||
private readonly List<string> _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<Transform> 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<Socket>();
|
|
||||||
Morphs = new List<Morph>();
|
|
||||||
Transforms = new List<Transform>();
|
|
||||||
}
|
|
||||||
|
|
||||||
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<UStaticMeshSocket>() 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<FPackageIndex>();
|
|
||||||
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<USkeletalMeshSocket>() 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<ResolvedObject> materials, IReadOnlyList<CStaticMeshLod> lods, Transform transform = null)
|
|
||||||
: this(export, materials, lods[_LOD_INDEX], lods[_LOD_INDEX].Verts, lods.Count, transform) {}
|
|
||||||
private Model(UObject export, IReadOnlyList<ResolvedObject> materials, IReadOnlyList<CSkelMeshLod> lods, Transform transform = null)
|
|
||||||
: this(export, materials, lods[_LOD_INDEX], lods[_LOD_INDEX].Verts, lods.Count, transform) {}
|
|
||||||
private Model(UObject export, IReadOnlyList<ResolvedObject> materials, CBaseMeshLod lod, IReadOnlyList<CMeshVertex> 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<Matrix4x4>(instanceMatrix, BufferTarget.ArrayBuffer);
|
|
||||||
_vao.BindInstancing(); // VertexAttributePointer
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Setup(Options options)
|
|
||||||
{
|
|
||||||
_handle = GL.CreateProgram();
|
|
||||||
var broken = GL.GetInteger(GetPName.MaxTextureCoords) == 0;
|
|
||||||
|
|
||||||
_ebo = new BufferObject<uint>(Indices, BufferTarget.ElementArrayBuffer);
|
|
||||||
_vbo = new BufferObject<float>(Vertices, BufferTarget.ArrayBuffer);
|
|
||||||
_vao = new VertexArrayObject<float, uint>(_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<float>(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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
99
FModel/Views/Snooper/Models/SkeletalModel.cs
Normal file
99
FModel/Views/Snooper/Models/SkeletalModel.cs
Normal file
|
|
@ -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<float> _morphVbo;
|
||||||
|
|
||||||
|
public readonly Skeleton Skeleton;
|
||||||
|
public readonly List<Morph> 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<FPackageIndex>();
|
||||||
|
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<USkeletalMeshSocket>() is not { } socket) continue;
|
||||||
|
Sockets.Add(new Socket(socket));
|
||||||
|
}
|
||||||
|
|
||||||
|
Morphs = new List<Morph>();
|
||||||
|
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<float>(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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
using CUE4Parse_Conversion.Meshes.PSK;
|
using CUE4Parse_Conversion.Meshes.PSK;
|
||||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||||
|
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
|
||||||
using FModel.Views.Snooper.Shading;
|
using FModel.Views.Snooper.Shading;
|
||||||
|
|
||||||
namespace FModel.Views.Snooper.Models;
|
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];
|
Indices = new uint[lod.Indices.Value.Length];
|
||||||
for (int i = 0; i < Indices.Length; i++)
|
for (int i = 0; i < Indices.Length; i++)
|
||||||
|
|
@ -44,5 +45,18 @@ public class Cube : Model
|
||||||
Sections[0] = new Section(0, Indices.Length, 0);
|
Sections[0] = new Section(0, Indices.Length, 0);
|
||||||
|
|
||||||
AddInstance(Transform.Identity);
|
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<UStaticMeshSocket>() is not { } socket) continue;
|
||||||
|
Sockets.Add(new Socket(socket));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
380
FModel/Views/Snooper/Models/UModel.cs
Normal file
380
FModel/Views/Snooper/Models/UModel.cs
Normal file
|
|
@ -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<VertexAttribute> _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<uint> Ebo { get; set; }
|
||||||
|
public BufferObject<float> Vbo { get; set; }
|
||||||
|
public BufferObject<Matrix4x4> MatrixVbo { get; set; }
|
||||||
|
public VertexArrayObject<float, uint> 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<Transform> Transforms { get; }
|
||||||
|
public Attachment Attachments { get; }
|
||||||
|
|
||||||
|
public FBox Box;
|
||||||
|
public readonly List<Socket> 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<Socket>();
|
||||||
|
Transforms = new List<Transform>();
|
||||||
|
}
|
||||||
|
|
||||||
|
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<Socket>();
|
||||||
|
Transforms = new List<Transform>();
|
||||||
|
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<ResolvedObject> materials, IReadOnlyList<CMeshVertex> 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<uint>(Indices, BufferTarget.ElementArrayBuffer);
|
||||||
|
Vbo = new BufferObject<float>(Vertices, BufferTarget.ArrayBuffer);
|
||||||
|
Vao = new VertexArrayObject<float, uint>(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<Matrix4x4>(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using CUE4Parse_Conversion;
|
|
||||||
using CUE4Parse_Conversion.Textures;
|
using CUE4Parse_Conversion.Textures;
|
||||||
using CUE4Parse.UE4.Assets.Exports;
|
|
||||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||||
using FModel.Settings;
|
using FModel.Settings;
|
||||||
|
|
@ -22,7 +19,7 @@ public class Options
|
||||||
public int SelectedMorph { get; private set; }
|
public int SelectedMorph { get; private set; }
|
||||||
public int SelectedAnimation{ get; private set; }
|
public int SelectedAnimation{ get; private set; }
|
||||||
|
|
||||||
public readonly Dictionary<FGuid, Model> Models;
|
public readonly Dictionary<FGuid, UModel> Models;
|
||||||
public readonly Dictionary<FGuid, Texture> Textures;
|
public readonly Dictionary<FGuid, Texture> Textures;
|
||||||
public readonly List<Light> Lights;
|
public readonly List<Light> Lights;
|
||||||
|
|
||||||
|
|
@ -31,12 +28,11 @@ public class Options
|
||||||
|
|
||||||
public readonly Dictionary<string, Texture> Icons;
|
public readonly Dictionary<string, Texture> Icons;
|
||||||
|
|
||||||
private readonly ETexturePlatform _platform;
|
|
||||||
private readonly string _game;
|
private readonly string _game;
|
||||||
|
|
||||||
public Options()
|
public Options()
|
||||||
{
|
{
|
||||||
Models = new Dictionary<FGuid, Model>();
|
Models = new Dictionary<FGuid, UModel>();
|
||||||
Textures = new Dictionary<FGuid, Texture>();
|
Textures = new Dictionary<FGuid, Texture>();
|
||||||
Lights = new List<Light>();
|
Lights = new List<Light>();
|
||||||
|
|
||||||
|
|
@ -60,7 +56,6 @@ public class Options
|
||||||
["tl_next"] = new ("tl_next"),
|
["tl_next"] = new ("tl_next"),
|
||||||
};
|
};
|
||||||
|
|
||||||
_platform = UserSettings.Default.CurrentDir.TexturePlatform;
|
|
||||||
_game = Services.ApplicationService.ApplicationView.CUE4Parse.Provider.InternalGameName.ToUpper();
|
_game = Services.ApplicationService.ApplicationView.CUE4Parse.Provider.InternalGameName.ToUpper();
|
||||||
|
|
||||||
SelectModel(Guid.Empty);
|
SelectModel(Guid.Empty);
|
||||||
|
|
@ -107,27 +102,30 @@ public class Options
|
||||||
|
|
||||||
public void RemoveModel(FGuid guid)
|
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);
|
DetachAndRemoveModels(model, true);
|
||||||
model.Dispose();
|
model.Dispose();
|
||||||
Models.Remove(guid);
|
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 socket in model.Sockets.ToList())
|
||||||
{
|
{
|
||||||
foreach (var info in socket.AttachedModels)
|
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);
|
RemoveModel(info.Guid);
|
||||||
}
|
}
|
||||||
else if (detach) attachedModel.SafeDetachModel(model);
|
else if (detach) attachedModel.Attachments.SafeDetach(model, t);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (socket.IsVirtual)
|
if (socket.IsVirtual)
|
||||||
|
|
@ -152,17 +150,20 @@ public class Options
|
||||||
{
|
{
|
||||||
foreach (var guid in animation.AttachedModels)
|
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);
|
animatedModel.Skeleton.ResetAnimatedData(true);
|
||||||
DetachAndRemoveModels(animatedModel, false);
|
DetachAndRemoveModels(animatedModel, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
animation.Dispose();
|
animation.Dispose();
|
||||||
}
|
}
|
||||||
foreach (var kvp in Models.ToList().Where(kvp => kvp.Value.IsAnimatedProp))
|
|
||||||
{
|
foreach (var kvp in Models)
|
||||||
RemoveModel(kvp.Key);
|
if (kvp.Value.IsProp)
|
||||||
}
|
RemoveModel(kvp.Key);
|
||||||
|
|
||||||
Animations.Clear();
|
Animations.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -171,7 +172,7 @@ public class Options
|
||||||
SelectedSection = index;
|
SelectedSection = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SelectMorph(int index, Model model)
|
public void SelectMorph(int index, SkeletalModel model)
|
||||||
{
|
{
|
||||||
SelectedMorph = index;
|
SelectedMorph = index;
|
||||||
model.UpdateMorph(SelectedMorph);
|
model.UpdateMorph(SelectedMorph);
|
||||||
|
|
@ -191,8 +192,8 @@ public class Options
|
||||||
return texture != null;
|
return texture != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryGetModel(out Model model) => Models.TryGetValue(SelectedModel, out model);
|
public bool TryGetModel(out UModel model) => Models.TryGetValue(SelectedModel, out model);
|
||||||
public bool TryGetModel(FGuid guid, out Model model) => Models.TryGetValue(guid, 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(out Section section) => TryGetSection(SelectedModel, out section);
|
||||||
public bool TryGetSection(FGuid guid, out Section section)
|
public bool TryGetSection(FGuid guid, out Section section)
|
||||||
|
|
@ -205,7 +206,7 @@ public class Options
|
||||||
section = null;
|
section = null;
|
||||||
return false;
|
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)
|
if (SelectedSection >= 0 && SelectedSection < model.Sections.Length)
|
||||||
section = model.Sections[SelectedSection]; else section = null;
|
section = model.Sections[SelectedSection]; else section = null;
|
||||||
|
|
@ -222,22 +223,6 @@ public class Options
|
||||||
Services.ApplicationService.ApplicationView.CUE4Parse.ModelIsWaitingAnimation = value;
|
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()
|
public void ResetModelsLightsAnimations()
|
||||||
{
|
{
|
||||||
foreach (var model in Models.Values)
|
foreach (var model in Models.Values)
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ using FModel.Views.Snooper.Lights;
|
||||||
using FModel.Views.Snooper.Models;
|
using FModel.Views.Snooper.Models;
|
||||||
using FModel.Views.Snooper.Shading;
|
using FModel.Views.Snooper.Shading;
|
||||||
using OpenTK.Windowing.GraphicsLibraryFramework;
|
using OpenTK.Windowing.GraphicsLibraryFramework;
|
||||||
|
using UModel = FModel.Views.Snooper.Models.UModel;
|
||||||
|
|
||||||
namespace FModel.Views.Snooper;
|
namespace FModel.Views.Snooper;
|
||||||
|
|
||||||
|
|
@ -91,6 +92,7 @@ public class Renderer : IDisposable
|
||||||
LoadWorld(cancellationToken, wd, Transform.Identity);
|
LoadWorld(cancellationToken, wd, Transform.Identity);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
SetupCamera();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Swap(UMaterialInstance unrealMaterial)
|
public void Swap(UMaterialInstance unrealMaterial)
|
||||||
|
|
@ -104,7 +106,7 @@ public class Renderer : IDisposable
|
||||||
public void Animate(UObject anim) => Animate(anim, Options.SelectedModel);
|
public void Animate(UObject anim) => Animate(anim, Options.SelectedModel);
|
||||||
private void Animate(UObject anim, FGuid guid)
|
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;
|
return;
|
||||||
|
|
||||||
float maxElapsedTime;
|
float maxElapsedTime;
|
||||||
|
|
@ -141,32 +143,39 @@ public class Renderer : IDisposable
|
||||||
t.Scale = offset.Scale3D;
|
t.Scale = offset.Scale3D;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UModel addedModel = null;
|
||||||
switch (export)
|
switch (export)
|
||||||
{
|
{
|
||||||
case UStaticMesh st:
|
case UStaticMesh st:
|
||||||
{
|
{
|
||||||
guid = st.LightingGuid;
|
guid = st.LightingGuid;
|
||||||
if (Options.TryGetModel(guid, out var instancedModel))
|
if (Options.TryGetModel(guid, out addedModel))
|
||||||
instancedModel.AddInstance(t);
|
{
|
||||||
|
addedModel.AddInstance(t);
|
||||||
|
}
|
||||||
else if (st.TryConvert(out var mesh))
|
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;
|
break;
|
||||||
}
|
}
|
||||||
case USkeletalMesh sk:
|
case USkeletalMesh sk:
|
||||||
{
|
{
|
||||||
guid = Guid.NewGuid();
|
guid = Guid.NewGuid();
|
||||||
if (!Options.Models.ContainsKey(guid) && sk.TryConvert(out var mesh))
|
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;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
throw new ArgumentException();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Options.TryGetModel(guid, out var addedModel))
|
if (addedModel == null)
|
||||||
continue;
|
throw new ArgumentException("Unknown model type");
|
||||||
|
|
||||||
addedModel.IsAnimatedProp = true;
|
addedModel.IsProp = true;
|
||||||
if (notifyClass.TryGetValue(out UObject skeletalMeshPropAnimation, "SkeletalMeshPropAnimation", "Animation"))
|
if (notifyClass.TryGetValue(out UObject skeletalMeshPropAnimation, "SkeletalMeshPropAnimation", "Animation"))
|
||||||
Animate(skeletalMeshPropAnimation, guid);
|
Animate(skeletalMeshPropAnimation, guid);
|
||||||
if (notifyClass.TryGetValue(out FName socketName, "SocketName"))
|
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);
|
var s = new Socket($"ANIM_{addedModel.Name}", socketName, t, true);
|
||||||
model.Sockets.Add(s);
|
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;
|
break;
|
||||||
|
|
@ -231,7 +241,7 @@ public class Renderer : IDisposable
|
||||||
// render model pass
|
// render model pass
|
||||||
foreach (var model in Options.Models.Values)
|
foreach (var model in Options.Models.Values)
|
||||||
{
|
{
|
||||||
if (!model.Show) continue;
|
if (!model.IsVisible) continue;
|
||||||
model.Render(_shader);
|
model.Render(_shader);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -249,7 +259,7 @@ public class Renderer : IDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
// outline pass
|
// outline pass
|
||||||
if (Options.TryGetModel(out var selected) && selected.Show)
|
if (Options.TryGetModel(out var selected) && selected.IsVisible)
|
||||||
{
|
{
|
||||||
_outline.Render(viewMatrix, CameraOp.Position, projMatrix);
|
_outline.Render(viewMatrix, CameraOp.Position, projMatrix);
|
||||||
selected.Render(_outline, true);
|
selected.Render(_outline, true);
|
||||||
|
|
@ -265,22 +275,23 @@ public class Renderer : IDisposable
|
||||||
foreach (var animation in Options.Animations)
|
foreach (var animation in Options.Animations)
|
||||||
{
|
{
|
||||||
animation.TimeCalculation(Options.Tracker.ElapsedTime);
|
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)
|
foreach (var model in Options.Models.Values)
|
||||||
{
|
{
|
||||||
model.UpdateMatrices(Options);
|
model.Update(Options);
|
||||||
}
|
}
|
||||||
|
|
||||||
CameraOp.Modify(wnd.KeyboardState, deltaSeconds);
|
CameraOp.Modify(wnd.KeyboardState, deltaSeconds);
|
||||||
|
|
||||||
if (wnd.KeyboardState.IsKeyPressed(Keys.Z) &&
|
if (wnd.KeyboardState.IsKeyPressed(Keys.Z) &&
|
||||||
Options.TryGetModel(out var selectedModel) &&
|
Options.TryGetModel(out var selectedModel) &&
|
||||||
selectedModel.HasSkeleton)
|
selectedModel is SkeletalModel)
|
||||||
{
|
{
|
||||||
Options.RemoveAnimations();
|
Options.RemoveAnimations();
|
||||||
Options.AnimateMesh(true);
|
Options.AnimateMesh(true);
|
||||||
|
|
@ -309,9 +320,8 @@ public class Renderer : IDisposable
|
||||||
if (!original.TryConvert(out var mesh))
|
if (!original.TryConvert(out var mesh))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Options.Models[guid] = new Model(original, mesh);
|
Options.Models[guid] = new StaticModel(original, mesh);
|
||||||
Options.SelectModel(guid);
|
Options.SelectModel(guid);
|
||||||
SetupCamera(Options.Models[guid].Box);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadSkeletalMesh(USkeletalMesh original)
|
private void LoadSkeletalMesh(USkeletalMesh original)
|
||||||
|
|
@ -319,14 +329,14 @@ public class Renderer : IDisposable
|
||||||
var guid = new FGuid((uint) original.GetFullName().GetHashCode());
|
var guid = new FGuid((uint) original.GetFullName().GetHashCode());
|
||||||
if (Options.Models.ContainsKey(guid) || !original.TryConvert(out var mesh)) return;
|
if (Options.Models.ContainsKey(guid) || !original.TryConvert(out var mesh)) return;
|
||||||
|
|
||||||
Options.Models[guid] = new Model(original, mesh);
|
var skeletalModel = new SkeletalModel(original, mesh);
|
||||||
|
Options.Models[guid] = skeletalModel;
|
||||||
Options.SelectModel(guid);
|
Options.SelectModel(guid);
|
||||||
SetupCamera(Options.Models[guid].Box);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadMaterialInstance(UMaterialInstance original)
|
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;
|
return;
|
||||||
|
|
||||||
var guid = editorCube.LightingGuid;
|
var guid = editorCube.LightingGuid;
|
||||||
|
|
@ -340,12 +350,15 @@ public class Renderer : IDisposable
|
||||||
if (!editorCube.TryConvert(out var mesh))
|
if (!editorCube.TryConvert(out var mesh))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Options.Models[guid] = new Cube(mesh, original);
|
Options.Models[guid] = new StaticModel(original, mesh);
|
||||||
Options.SelectModel(guid);
|
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)
|
private void LoadWorld(CancellationToken cancellationToken, UWorld original, Transform transform)
|
||||||
{
|
{
|
||||||
|
|
@ -432,8 +445,8 @@ public class Renderer : IDisposable
|
||||||
}
|
}
|
||||||
else if (m.TryConvert(out var mesh))
|
else if (m.TryConvert(out var mesh))
|
||||||
{
|
{
|
||||||
model = new Model(m, mesh, t);
|
model = new StaticModel(m, mesh, t);
|
||||||
model.TwoSided = actor.GetOrDefault("bMirrored", staticMeshComp.GetOrDefault("bDisallowMeshPaintPerInstance", model.TwoSided));
|
model.IsTwoSided = actor.GetOrDefault("bMirrored", staticMeshComp.GetOrDefault("bDisallowMeshPaintPerInstance", model.IsTwoSided));
|
||||||
|
|
||||||
if (actor.TryGetValue(out FPackageIndex baseMaterial, "BaseMaterial") &&
|
if (actor.TryGetValue(out FPackageIndex baseMaterial, "BaseMaterial") &&
|
||||||
actor.TryGetAllValues(out FPackageIndex[] textureData, "TextureData"))
|
actor.TryGetAllValues(out FPackageIndex[] textureData, "TextureData"))
|
||||||
|
|
|
||||||
|
|
@ -316,7 +316,7 @@ public class Material : IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ImGuiTextures(Dictionary<string, Texture> icons, Model model)
|
public bool ImGuiTextures(Dictionary<string, Texture> icons, UModel model)
|
||||||
{
|
{
|
||||||
if (ImGui.BeginTable("material_textures", 2))
|
if (ImGui.BeginTable("material_textures", 2))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -184,7 +184,7 @@ public class SnimGui
|
||||||
{
|
{
|
||||||
foreach (var model in s.Renderer.Options.Models.Values)
|
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();
|
ImGui.TableHeadersRow();
|
||||||
|
|
||||||
var i = 0;
|
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.PushID(i);
|
||||||
ImGui.TableNextRow();
|
ImGui.TableNextRow();
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
if (!model.Show)
|
if (!model.IsVisible)
|
||||||
ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, ImGui.GetColorU32(new Vector4(1, 0, 0, .5f)));
|
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)));
|
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.TableSetBgColor(ImGuiTableBgTarget.RowBg0, ImGui.GetColorU32(new Vector4(1, 1, 0, .5f)));
|
||||||
|
|
||||||
ImGui.Text(model.TransformsCount.ToString("D"));
|
ImGui.Text(model.TransformsCount.ToString("D"));
|
||||||
|
|
@ -426,16 +426,16 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
||||||
Popup(() =>
|
Popup(() =>
|
||||||
{
|
{
|
||||||
s.Renderer.Options.SelectModel(guid);
|
s.Renderer.Options.SelectModel(guid);
|
||||||
if (ImGui.MenuItem("Show", null, model.Show)) model.Show = !model.Show;
|
if (ImGui.MenuItem("Show", null, model.IsVisible)) model.IsVisible = !model.IsVisible;
|
||||||
if (ImGui.MenuItem("Wireframe", null, model.Wireframe)) model.Wireframe = !model.Wireframe;
|
if (ImGui.MenuItem("Wireframe", null, model.ShowWireframe)) model.ShowWireframe = !model.ShowWireframe;
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
if (ImGui.MenuItem("Save"))
|
if (ImGui.MenuItem("Save"))
|
||||||
{
|
{
|
||||||
s.WindowShouldFreeze(true);
|
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);
|
s.WindowShouldFreeze(false);
|
||||||
}
|
}
|
||||||
if (ImGui.MenuItem("Animate", model.HasSkeleton))
|
if (ImGui.MenuItem("Animate", model is SkeletalModel))
|
||||||
{
|
{
|
||||||
if (_swapper.IsAware)
|
if (_swapper.IsAware)
|
||||||
{
|
{
|
||||||
|
|
@ -455,10 +455,10 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
||||||
_swapper.Value = true;
|
_swapper.Value = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ImGui.MenuItem("Bone Hierarchy", model.HasSkeleton))
|
if (ImGui.MenuItem("Skeleton Tree", model is SkeletalModel))
|
||||||
{
|
{
|
||||||
_bh_open = true;
|
_bh_open = true;
|
||||||
ImGui.SetWindowFocus("Bone Hierarchy");
|
ImGui.SetWindowFocus("Skeleton Tree");
|
||||||
}
|
}
|
||||||
if (ImGui.MenuItem("Teleport To"))
|
if (ImGui.MenuItem("Teleport To"))
|
||||||
{
|
{
|
||||||
|
|
@ -473,8 +473,8 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
||||||
});
|
});
|
||||||
|
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.Image(s.Renderer.Options.Icons[model.AttachIcon].GetPointer(), new Vector2(_tableWidth));
|
ImGui.Image(s.Renderer.Options.Icons[model.Attachments.Icon].GetPointer(), new Vector2(_tableWidth));
|
||||||
TooltipCopy(model.AttachTooltip);
|
TooltipCopy(model.Attachments.Tooltip);
|
||||||
|
|
||||||
ImGui.PopID();
|
ImGui.PopID();
|
||||||
i++;
|
i++;
|
||||||
|
|
@ -501,14 +501,14 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
||||||
{
|
{
|
||||||
var isAttached = socket.AttachedModels.Contains(info);
|
var isAttached = socket.AttachedModels.Contains(info);
|
||||||
ImGui.PushID(i);
|
ImGui.PushID(i);
|
||||||
ImGui.BeginDisabled(selectedModel.IsAttached && !isAttached);
|
ImGui.BeginDisabled(selectedModel.Attachments.IsAttached && !isAttached);
|
||||||
switch (isAttached)
|
switch (isAttached)
|
||||||
{
|
{
|
||||||
case false when ImGui.Button($"Attach to '{socket.Name}'"):
|
case false when ImGui.Button($"Attach to '{socket.Name}'"):
|
||||||
selectedModel.AttachModel(model, socket, info);
|
selectedModel.Attachments.Attach(model, selectedModel.GetTransform(), socket, info);
|
||||||
break;
|
break;
|
||||||
case true when ImGui.Button($"Detach from '{socket.Name}'"):
|
case true when ImGui.Button($"Detach from '{socket.Name}'"):
|
||||||
selectedModel.DetachModel(model, socket, info);
|
selectedModel.Attachments.Detach(model, selectedModel.GetTransform(), socket, info);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
ImGui.EndDisabled();
|
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("Entity");ImGui.Text($" : ({model.Type}) {model.Name}");
|
||||||
Layout("Guid");ImGui.Text($" : {s.Renderer.Options.SelectedModel.ToString(EGuidFormats.UniqueObjectGuid)}");
|
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("Skeleton");ImGui.Text($" : {skeletalModel.Skeleton.Name}");
|
||||||
Layout("Bones");ImGui.Text($" : x{model.Skeleton.BoneCount}");
|
Layout("Bones");ImGui.Text($" : x{skeletalModel.Skeleton.BoneCount}");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Layout("Two Sided");ImGui.Text($" : {model.TwoSided}");
|
Layout("Two Sided");ImGui.Text($" : {model.IsTwoSided}");
|
||||||
}
|
}
|
||||||
Layout("Sockets");ImGui.Text($" : x{model.Sockets.Count}");
|
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 (ImGui.BeginTabItem("Morph Targets"))
|
||||||
{
|
{
|
||||||
if (model.HasMorphTargets)
|
if (model is SkeletalModel { HasMorphTargets: true } skeletalModel)
|
||||||
{
|
{
|
||||||
const float width = 10;
|
const float width = 10;
|
||||||
var region = ImGui.GetContentRegionAvail();
|
var region = ImGui.GetContentRegionAvail();
|
||||||
|
|
@ -628,12 +628,12 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
||||||
|
|
||||||
if (ImGui.BeginListBox("", box))
|
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);
|
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();
|
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.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(2f, 0f));
|
||||||
ImGui.SameLine(); ImGui.PushID(99);
|
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.PopID(); ImGui.PopStyleVar();
|
||||||
ImGui.Spacing();
|
ImGui.Spacing();
|
||||||
ImGui.Text($"Time: {model.MorphTime:P}%");
|
ImGui.Text($"Time: {skeletalModel.MorphTime:P}%");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else CenteredTextColored(_errorColor, "Selected Mesh Has No Morph Targets");
|
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();
|
ImGui.PopStyleVar();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawMaterialInspector(Dictionary<string, Texture> icons, Model model, Section section)
|
private void DrawMaterialInspector(Dictionary<string, Texture> icons, UModel model, Section section)
|
||||||
{
|
{
|
||||||
var material = model.Materials[section.MaterialIndex];
|
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)
|
private void DrawBoneHierarchy(Snooper s)
|
||||||
{
|
{
|
||||||
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
|
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();
|
// ImGui.SeparatorText("Options");
|
||||||
if (ImGui.BeginTable("bone_hierarchy", 2, ImGuiTableFlags.NoSavedSettings | ImGuiTableFlags.RowBg, ImGui.GetContentRegionAvail(), ImGui.GetWindowWidth()))
|
// 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("Bone", ImGuiTableColumnFlags.WidthStretch);
|
||||||
ImGui.TableSetupColumn("", ImGuiTableColumnFlags.NoHeaderWidth | ImGuiTableColumnFlags.WidthFixed, _tableWidth);
|
ImGui.TableSetupColumn("", ImGuiTableColumnFlags.NoHeaderWidth | ImGuiTableColumnFlags.WidthFixed, _tableWidth);
|
||||||
model.Skeleton.ImGuiBoneHierarchy();
|
skeletalModel.Skeleton.ImGuiBoneHierarchy();
|
||||||
ImGui.EndTable();
|
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<Dictionary<string, Texture>, Model> content, bool styled = true)
|
private void MeshWindow(string name, Renderer renderer, Action<Dictionary<string, Texture>, UModel> content, bool styled = true)
|
||||||
{
|
{
|
||||||
Window(name, () =>
|
Window(name, () =>
|
||||||
{
|
{
|
||||||
|
|
@ -837,7 +847,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
||||||
}, styled);
|
}, styled);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SectionWindow(string name, Renderer renderer, Action<Dictionary<string, Texture>, Model, Section> content, bool styled = true)
|
private void SectionWindow(string name, Renderer renderer, Action<Dictionary<string, Texture>, UModel, Section> content, bool styled = true)
|
||||||
{
|
{
|
||||||
MeshWindow(name, renderer, (icons, model) =>
|
MeshWindow(name, renderer, (icons, model) =>
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user