fuck you bones position

that's a problem for future me
This commit is contained in:
4sval 2022-11-13 22:10:22 +01:00
parent 25de50818d
commit ad6c4b9474
13 changed files with 606 additions and 421 deletions

@ -1 +1 @@
Subproject commit cc744cfb54e219d7f5d9440d30c4cd55b56afb3c
Subproject commit 7a13a4e1c46e8a084f681e1a1a8702de695da3fa

View File

@ -194,6 +194,7 @@ outputColor = color * texture(in_fontTexture, texCoord);
ImGui.Render();
RenderImDrawData(ImGui.GetDrawData());
}
CheckGLError("End of frame");
}
/// <summary>

View File

@ -77,9 +77,15 @@ public partial class MainWindow
_discordHandler.Initialize(_applicationView.CUE4Parse.Game);
#if DEBUG
await _threadWorkerView.Begin(cancellationToken =>
_applicationView.CUE4Parse.Extract(cancellationToken,
"FortniteGame/Content/Athena/Artemis/Maps/Buildings/5x5/Artemis_5x5_Shop_AB.umap"));
// await _threadWorkerView.Begin(cancellationToken =>
// _applicationView.CUE4Parse.Extract(cancellationToken,
// "FortniteGame/Content/Characters/Player/Female/Medium/Bodies/F_MED_RoseDust/Meshes/F_MED_RoseDust.uasset"));
// await _threadWorkerView.Begin(cancellationToken =>
// _applicationView.CUE4Parse.Extract(cancellationToken,
// "fortnitegame/Content/Accessories/FORT_Backpacks/Backpack_M_MED_Despair/Meshes/M_MED_Despair_Pack.uasset"));
// await _threadWorkerView.Begin(cancellationToken =>
// _applicationView.CUE4Parse.Extract(cancellationToken,
// "FortniteGame/Content/Animation/Game/MainPlayer/Emotes/Acrobatic_Superhero/Emote_AcrobaticSuperhero_CMM.uasset"));
#endif
}

View File

@ -6,14 +6,18 @@ layout (location = 3) in vec3 vTangent;
layout (location = 4) in vec2 vTexCoords;
layout (location = 5) in float vTexLayer;
layout (location = 6) in vec4 vColor;
layout (location = 7) in ivec4 vBoneIds;
layout (location = 8) in vec4 vWeights;
layout (location = 7) in vec4 vBoneIds;
layout (location = 8) in vec4 vBoneWeights;
layout (location = 9) in mat4 vInstanceMatrix;
layout (location = 13) in vec3 vMorphTarget;
//const int MAX_BONES = 0;
//const int MAX_BONE_INFLUENCE = 0;
uniform mat4 uView;
uniform mat4 uProjection;
uniform float uMorphTime;
//uniform mat4 uFinalBonesMatrix[MAX_BONES];
out vec3 fPos;
out vec3 fNormal;
@ -25,6 +29,18 @@ out vec4 fColor;
void main()
{
vec4 pos = vec4(mix(vPos, vMorphTarget, uMorphTime), 1.0);
// for(int i = 0 ; i < MAX_BONE_INFLUENCE; i++)
// {
// if(vBoneIds[i] == -1) continue;
// if(vBoneIds[i] >= MAX_BONES)
// {
// break;
// }
//
// vec4 localPos = uFinalBonesMatrix[int(vBoneIds[i])] * pos;
// pos += localPos * vBoneWeights[i];
// }
gl_Position = uProjection * uView * vInstanceMatrix * pos;
fPos = vec3(vInstanceMatrix * pos);

View File

@ -72,6 +72,13 @@ public class CUE4ParseViewModel : ViewModel
set => SetProperty(ref _modelIsOverwritingMaterial, value);
}
private bool _modelIsWaitingAnimation;
public bool ModelIsWaitingAnimation
{
get => _modelIsWaitingAnimation;
set => SetProperty(ref _modelIsWaitingAnimation, value);
}
private Snooper _snooper;
public Snooper SnooperViewer
{
@ -790,6 +797,12 @@ public class CUE4ParseViewModel : ViewModel
SnooperViewer.Run();
return true;
}
case UAnimSequence a when ModelIsWaitingAnimation:
{
SnooperViewer.Renderer.Animate(a);
SnooperViewer.Run();
return true;
}
case UStaticMesh when UserSettings.Default.SaveStaticMeshes:
case USkeletalMesh when UserSettings.Default.SaveSkeletalMeshes:
case UMaterialInstance when UserSettings.Default.SaveMaterials:

View File

@ -0,0 +1,64 @@
using System;
using System.Numerics;
using CUE4Parse_Conversion.Animations;
using CUE4Parse.UE4.Objects.Core.Math;
namespace FModel.Views.Snooper;
public class Animation : IDisposable
{
public float CurrentTime;
public float DeltaTime;
public CAnimSet CurrentAnimation;
public Matrix4x4[] FinalBonesMatrix;
public Animation(CAnimSet anim)
{
CurrentTime = 0f;
CurrentAnimation = anim;
FinalBonesMatrix = new Matrix4x4[anim.TrackBoneNames.Length];
for (int i = 0; i < FinalBonesMatrix.Length; i++)
{
FinalBonesMatrix[i] = Matrix4x4.Identity;
}
}
public void UpdateAnimation(float deltaTime)
{
DeltaTime = deltaTime;
if (CurrentAnimation != null)
{
CurrentTime = deltaTime;
CalculateBoneTransform();
}
}
public void CalculateBoneTransform()
{
var sequence = CurrentAnimation.Sequences[0];
for (int boneIndex = 0; boneIndex < FinalBonesMatrix.Length; boneIndex++)
{
var boneOrientation = FQuat.Identity;
var bonePosition = FVector.ZeroVector;
sequence.Tracks[boneIndex].GetBonePosition(CurrentTime, sequence.NumFrames, false, ref bonePosition, ref boneOrientation);
boneOrientation *= CurrentAnimation.BonePositions[boneIndex].Orientation;
bonePosition = boneOrientation.RotateVector(bonePosition);
bonePosition *= Constants.SCALE_DOWN_RATIO;
if (CurrentAnimation.TrackBoneNames[boneIndex].Text == "pelvis")
{
}
FinalBonesMatrix[boneIndex] =
Matrix4x4.CreateFromQuaternion(boneOrientation) *
Matrix4x4.CreateTranslation(bonePosition.ToMapVector());
}
}
public void Dispose()
{
throw new NotImplementedException();
}
}

View File

@ -28,14 +28,15 @@ public class Model : IDisposable
public readonly string Name;
public readonly string Type;
public readonly bool HasVertexColors;
public readonly bool HasBones;
public readonly bool HasMorphTargets;
public readonly int NumTexCoords;
public uint[] Indices;
public float[] Vertices;
public Section[] Sections;
public Material[] Materials;
public readonly List<CSkelMeshBone> Skeleton;
public bool HasSkeleton => Skeleton is { IsLoaded: true };
public readonly Skeleton Skeleton;
public int TransformsCount;
public readonly List<Transform> Transforms;
@ -50,7 +51,6 @@ public class Model : IDisposable
public bool bVertexNormals;
public bool bVertexTangent;
public bool bVertexTexCoords;
public bool DisplayBones;
public int SelectedInstance;
public float MorphTime;
@ -63,10 +63,10 @@ public class Model : IDisposable
}
public Model(string name, string type, ResolvedObject[] materials, CStaticMesh staticMesh) : this(name, type, materials, staticMesh, Transform.Identity) {}
public Model(string name, string type, ResolvedObject[] materials, CStaticMesh staticMesh, Transform transform) : this(name, type, materials, staticMesh.LODs[0], staticMesh.LODs[0].Verts, transform) {}
public Model(string name, string type, ResolvedObject[] materials, CSkeletalMesh skeletalMesh) : this(name, type, materials, skeletalMesh, Transform.Identity) {}
public Model(string name, string type, ResolvedObject[] materials, CSkeletalMesh skeletalMesh, Transform transform) : this(name, type, materials, skeletalMesh.LODs[0], skeletalMesh.LODs[0].Verts, transform, skeletalMesh.RefSkeleton) {}
public Model(string name, string type, ResolvedObject[] materials, FPackageIndex[] morphTargets, CSkeletalMesh skeletalMesh) : this(name, type, materials, skeletalMesh)
public Model(string name, string type, ResolvedObject[] materials, CStaticMesh staticMesh, Transform transform) : this(name, type, materials, null, staticMesh.LODs[0], staticMesh.LODs[0].Verts, transform) {}
public Model(string name, string type, ResolvedObject[] materials, FPackageIndex skeleton, CSkeletalMesh skeletalMesh) : this(name, type, materials, skeleton, skeletalMesh, Transform.Identity) {}
public Model(string name, string type, ResolvedObject[] materials, FPackageIndex skeleton, CSkeletalMesh skeletalMesh, Transform transform) : this(name, type, materials, skeleton, skeletalMesh.LODs[0], skeletalMesh.LODs[0].Verts, transform) {}
public Model(string name, string type, ResolvedObject[] materials, FPackageIndex skeleton, FPackageIndex[] morphTargets, CSkeletalMesh skeletalMesh) : this(name, type, materials, skeleton, skeletalMesh)
{
if (morphTargets is not { Length: > 0 })
return;
@ -79,7 +79,7 @@ public class Model : IDisposable
}
}
private Model(string name, string type, ResolvedObject[] materials, CBaseMeshLod lod, CMeshVertex[] vertices, Transform transform, List<CSkelMeshBone> skeleton = null) : this(name, type)
private Model(string name, string type, ResolvedObject[] materials, FPackageIndex skeleton, CBaseMeshLod lod, CMeshVertex[] vertices, Transform transform = null) : this(name, type)
{
NumTexCoords = lod.NumTexCoords;
@ -96,10 +96,9 @@ public class Model : IDisposable
_vertexSize += 4; // + Color
}
if (skeleton is { Count: > 0 })
if (skeleton != null)
{
HasBones = true;
Skeleton = skeleton;
Skeleton = new Skeleton(skeleton);
_vertexSize += 8; // + BoneIds + BoneWeights
}
@ -146,7 +145,7 @@ public class Model : IDisposable
Vertices[baseIndex + count++] = color.A;
}
if (HasBones)
if (HasSkeleton)
{
var skelVert = (CSkelMeshVertex) vert;
var weightsHash = skelVert.UnpackWeights();
@ -165,7 +164,7 @@ public class Model : IDisposable
}
}
AddInstance(transform);
AddInstance(transform ?? Transform.Identity);
}
public void AddInstance(Transform transform)
@ -212,7 +211,7 @@ public class Model : IDisposable
_vao.VertexAttributePointer(4, 2, VertexAttribPointerType.Float, _vertexSize, 10); // uv
_vao.VertexAttributePointer(5, 1, VertexAttribPointerType.Float, _vertexSize, 12); // texture index
_vao.VertexAttributePointer(6, 4, VertexAttribPointerType.Float, _vertexSize, 13); // color
_vao.VertexAttributePointer(7, 4, VertexAttribPointerType.Int, _vertexSize, 17); // boneids
_vao.VertexAttributePointer(7, 4, VertexAttribPointerType.Float, _vertexSize, 17); // boneids
_vao.VertexAttributePointer(8, 4, VertexAttribPointerType.Float, _vertexSize, 21); // boneweights
SetupInstances(); // instanced models transform

View File

@ -50,4 +50,9 @@ public class Options
{
Services.ApplicationService.ApplicationView.CUE4Parse.ModelIsOverwritingMaterial = value;
}
public void AnimateMesh(bool value)
{
Services.ApplicationService.ApplicationView.CUE4Parse.ModelIsWaitingAnimation = value;
}
}

View File

@ -2,8 +2,10 @@
using System.Numerics;
using System.Threading;
using System.Windows;
using CUE4Parse_Conversion.Animations;
using CUE4Parse_Conversion.Meshes;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Animation;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
@ -55,6 +57,16 @@ public class Renderer : IDisposable
Settings.SwapMaterial(false);
}
public void Animate(UAnimSequence animSequence)
{
if (!Cache.Models.TryGetValue(Settings.SelectedModel, out var model) || !model.Skeleton.IsLoaded ||
model.Skeleton?.RefSkel.ConvertAnims(animSequence) is not { } anim || anim.Sequences.Count == 0)
return;
model.Skeleton.Anim = new Animation(anim);
Settings.AnimateMesh(false);
}
public void Setup()
{
_shader = new Shader();
@ -122,7 +134,7 @@ public class Renderer : IDisposable
var guid = Guid.NewGuid();
if (Cache.Models.ContainsKey(guid) || !original.TryConvert(out var mesh)) return null;
Cache.Models[guid] = new Model(original.Name, original.ExportType, original.Materials, original.MorphTargets, mesh);
Cache.Models[guid] = new Model(original.Name, original.ExportType, original.Materials, original.Skeleton, original.MorphTargets, mesh);
Settings.SelectModel(guid);
return SetupCamera(mesh.BoundingBox *= Constants.SCALE_DOWN_RATIO);
}

View File

@ -78,12 +78,14 @@ public class Shader : IDisposable
GL.Uniform1(location, value);
}
public unsafe void SetUniform(string name, Matrix4 value)
public unsafe void SetUniform(string name, Matrix4 value) => UniformMatrix4(name, (float*) &value);
public unsafe void SetUniform(string name, System.Numerics.Matrix4x4 value) => UniformMatrix4(name, (float*) &value);
public unsafe void UniformMatrix4(string name, float* value)
{
//A new overload has been created for setting a uniform so we can use the transform in our shader.
int location = GL.GetUniformLocation(_handle, name);
ThrowIfNotFound(location, name);
GL.UniformMatrix4(location, 1, false, (float*) &value);
GL.UniformMatrix4(location, 1, false, value);
}
public void SetUniform(string name, bool value) => SetUniform(name, Convert.ToUInt32(value));
@ -124,7 +126,7 @@ public class Shader : IDisposable
{
if (location == -1)
{
// throw new Exception($"{name} uniform not found on shader.");
throw new Exception($"{name} uniform not found on shader.");
}
}

View File

@ -0,0 +1,52 @@
using System;
using CUE4Parse.UE4.Assets.Exports.Animation;
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
using CUE4Parse.UE4.Objects.UObject;
namespace FModel.Views.Snooper;
public class Skeleton : IDisposable
{
public readonly USkeleton RefSkel;
public readonly bool IsLoaded;
public readonly Socket[] Sockets;
public Animation Anim;
public Skeleton(FPackageIndex package)
{
RefSkel = package.Load<USkeleton>();
if (RefSkel == null) return;
IsLoaded = true;
Sockets = new Socket[RefSkel.Sockets.Length];
for (int i = 0; i < Sockets.Length; i++)
{
if (RefSkel.Sockets[i].Load<USkeletalMeshSocket>() is not { } socket ||
!RefSkel.ReferenceSkeleton.FinalNameToIndexMap.TryGetValue(socket.BoneName.Text, out var boneIndex))
continue;
var t = RefSkel.ReferenceSkeleton.FinalRefBonePose[boneIndex];
var transform = Transform.Identity;
transform.Position = t.Translation.ToMapVector() * Constants.SCALE_DOWN_RATIO;
transform.Rotation = t.Rotator();
transform.Scale = t.Scale3D.ToMapVector();
Sockets[i] = new Socket(socket, transform);
}
}
public void SetUniform(Shader shader)
{
if (!IsLoaded) return;
for (var i = 0; i < Anim?.FinalBonesMatrix.Length; i++)
{
shader.SetUniform($"uFinalBonesMatrix[{i}]", Anim.FinalBonesMatrix[i]);
}
}
public void Dispose()
{
throw new NotImplementedException();
}
}

View File

@ -37,91 +37,27 @@ public class SnimGui
DrawDockSpace(s.Size);
DrawNavbar();
ImGui.Begin("Camera");
ImGui.DragFloat("Speed", ref s.Camera.Speed, 0.01f, 0.05f);
ImGui.DragFloat("Far Plane", ref s.Camera.Far, 0.1f, 5f, s.Camera.Far * 2f, "%.2f m", ImGuiSliderFlags.AlwaysClamp);
ImGui.End();
ImGui.Begin("World");
ImGui.Checkbox("Diffuse Only", ref s.Renderer.bDiffuseOnly);
ImGui.End();
ImGui.Begin("Timeline");
ImGui.End();
if (ImGui.Begin("Materials"))
{
PushStyleCompact();
var guid = s.Renderer.Settings.SelectedModel;
if (s.Renderer.Cache.Models.TryGetValue(guid, out var model) &&
s.Renderer.Settings.TryGetSection(model, out var section))
{
var material = model.Materials[section.MaterialIndex];
foreach ((string key, float value) in material.Parameters.Scalars)
{
ImGui.Text($"{key}: {value}");
}
ImGui.Spacing(); ImGui.Separator(); ImGui.Spacing();
foreach ((string key, FLinearColor value) in material.Parameters.Colors)
{
ImGui.Text(key); ImGui.SameLine();
ImGui.ColorButton(key, new Vector4(value.R, value.G, value.B, value.A));
}
}
else NoMeshSelected();
PopStyleCompact();
ImGui.End();
}
if (ImGui.Begin("Textures"))
{
PushStyleCompact();
var guid = s.Renderer.Settings.SelectedModel;
if (s.Renderer.Cache.Models.TryGetValue(guid, out var model) &&
s.Renderer.Settings.TryGetSection(model, out var section))
{
var material = model.Materials[section.MaterialIndex];
foreach ((string key, UUnrealMaterial value) in material.Parameters.Textures)
{
ImGui.Text($"{key}: {value.Name}");
}
}
else NoMeshSelected();
PopStyleCompact();
ImGui.End();
}
if (ImGui.Begin("Parameters"))
{
PushStyleCompact();
var guid = s.Renderer.Settings.SelectedModel;
if (s.Renderer.Cache.Models.TryGetValue(guid, out var model) &&
s.Renderer.Settings.TryGetSection(model, out var section))
{
const int width = 50;
var material = model.Materials[section.MaterialIndex];
ImGui.Checkbox("Show Section", ref section.Show);
ImGui.SetNextItemWidth(width); ImGui.DragFloat("Emissive Multiplier", ref material.EmissiveMult, .01f, 0f);
ImGui.SetNextItemWidth(width); ImGui.DragFloat("UV Scale", ref material.UVScale, .01f, 0f);
if (material.HasM)
{
ImGui.ColorEdit3("Skin Boost Color", ref material.M.SkinBoost.Color, ImGuiColorEditFlags.NoInputs);
ImGui.SetNextItemWidth(width); ImGui.DragFloat("Skin Boost Exponent", ref material.M.SkinBoost.Exponent, .01f, 0f);
ImGui.SetNextItemWidth(width); ImGui.DragFloat("AmbientOcclusion", ref material.M.AmbientOcclusion, .01f, 0f, 1f);
ImGui.SetNextItemWidth(width); ImGui.DragFloat("Cavity", ref material.M.Cavity, .01f, 0f, 1f);
}
}
else NoMeshSelected();
PopStyleCompact();
ImGui.End();
}
SectionWindow("Materials", s.Renderer, DrawMaterials);
SectionWindow("Textures", s.Renderer, DrawTextures);
SectionWindow("Parameters", s.Renderer, DrawParameters);
SectionWindow("UV Channels", s.Renderer, DrawUvChannels);
Window("Timeline", () => {});
Window("Camera", () =>
{
ImGui.DragFloat("Speed", ref s.Camera.Speed, 0.01f, 0.05f);
ImGui.DragFloat("Far Plane", ref s.Camera.Far, 0.1f, 5f, s.Camera.Far * 2f, "%.2f m", ImGuiSliderFlags.AlwaysClamp);
});
Window("World", () => ImGui.Checkbox("Diffuse Only", ref s.Renderer.bDiffuseOnly));
Window("Outliner", () => DrawOuliner(s));
Window("Sockets", () => DrawSockets(s));
MeshWindow("Transform", s.Renderer, model => DrawTransform(model, s.Camera.Speed / 100f));
MeshWindow("Details", s.Renderer, model => DrawDetails(model, s));
DrawUvChannels(s);
DrawTransform(s);
DrawDetails(s);
DrawOuliner(s);
Draw3DViewport(s);
// last render will always be on top
// order by decreasing importance
Controller.Render();
ImGuiController.CheckGLError("End of frame");
}
private void DrawDockSpace(Vector2i size)
@ -170,386 +106,438 @@ public class SnimGui
private void DrawOuliner(Snooper s)
{
if (ImGui.Begin("Outliner"))
if (ImGui.BeginTable("Items", 3, ImGuiTableFlags.Borders | ImGuiTableFlags.Resizable))
{
PushStyleCompact();
ImGui.TableSetupColumn("Instance", ImGuiTableColumnFlags.NoHeaderWidth | ImGuiTableColumnFlags.WidthFixed);
ImGui.TableSetupColumn("Channels", ImGuiTableColumnFlags.WidthFixed);
ImGui.TableSetupColumn("Name");
ImGui.TableHeadersRow();
if (ImGui.BeginTable("Items", 3, ImGuiTableFlags.Borders | ImGuiTableFlags.Resizable))
var i = 0;
foreach ((FGuid guid, Model model) in s.Renderer.Cache.Models)
{
ImGui.TableSetupColumn("Instance", ImGuiTableColumnFlags.NoHeaderWidth | ImGuiTableColumnFlags.WidthFixed);
ImGui.TableSetupColumn("Channels", ImGuiTableColumnFlags.WidthFixed);
ImGui.TableSetupColumn("Name");
ImGui.PushID(i);
ImGui.TableNextRow();
ImGui.TableNextColumn();
if (!model.Show)
{
ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, ImGui.GetColorU32(new Vector4(1, 0, 0, .5f)));
}
ImGui.Text(model.TransformsCount.ToString("D"));
ImGui.TableNextColumn();
ImGui.Text(model.NumTexCoords.ToString("D"));
ImGui.TableNextColumn();
model.IsSelected = s.Renderer.Settings.SelectedModel == guid;
if (ImGui.Selectable(model.Name, model.IsSelected, ImGuiSelectableFlags.SpanAllColumns))
{
s.Renderer.Settings.SelectModel(guid);
}
if (ImGui.BeginPopupContextItem())
{
s.Renderer.Settings.SelectModel(guid);
ImGui.BeginDisabled(!model.HasSkeleton);
if (ImGui.Selectable("Animate"))
{
s.Renderer.Settings.AnimateMesh(true);
s.WindowShouldClose(true, false);
}
ImGui.Separator();
ImGui.EndDisabled();
if (ImGui.Selectable("Delete")) s.Renderer.Cache.Models.Remove(guid);
if (ImGui.Selectable("Deselect")) s.Renderer.Settings.SelectModel(Guid.Empty);
if (ImGui.Selectable("Copy Name to Clipboard")) ImGui.SetClipboardText(model.Name);
ImGui.EndPopup();
}
ImGui.PopID();
i++;
}
ImGui.EndTable();
}
}
private void DrawSockets(Snooper s)
{
foreach (var model in s.Renderer.Cache.Models.Values)
{
if (!model.HasSkeleton) return;
if (ImGui.TreeNode($"{model.Name} [{model.Skeleton.Sockets.Length}]"))
{
foreach (var socket in model.Skeleton.Sockets)
{
ImGui.Text($"{socket.Name} attached to {socket.Bone}");
ImGui.Text($"P: {socket.Transform.Position}");
ImGui.Text($"R: {socket.Transform.Rotation}");
ImGui.Text($"S: {socket.Transform.Scale}");
if (ImGui.Button("Attach"))
{
var guid = s.Renderer.Settings.SelectedModel;
if (s.Renderer.Cache.Models.TryGetValue(guid, out var selected))
{
selected.Transforms[selected.SelectedInstance] = socket.Transform;
selected.UpdateMatrix(selected.SelectedInstance);
}
}
}
ImGui.TreePop();
}
}
}
private void DrawDetails(Model model, Snooper s)
{
ImGui.Text($"Entity: ({model.Type}) {model.Name}");
ImGui.Text($"Guid: {s.Renderer.Settings.SelectedModel.ToString(EGuidFormats.UniqueObjectGuid)}");
// if (model.Skeleton.Anim != null)
// {
// ImGui.DragFloat("Time", ref model.Skeleton.Anim.CurrentTime, 1.0f, 0.0f, 115.0f, "%.1f s");
// model.Skeleton.Anim.CalculateBoneTransform();
// }
ImGui.Columns(3, "Actions", false);
if (ImGui.Button("Go To"))
{
var instancePos = model.Transforms[model.SelectedInstance].Position;
s.Camera.Position = new Vector3(instancePos.X, instancePos.Y, instancePos.Z);
}
ImGui.NextColumn(); ImGui.Checkbox("Show", ref model.Show);
ImGui.NextColumn(); ImGui.BeginDisabled(!model.Show); ImGui.Checkbox("Wire", ref model.Wireframe); ImGui.EndDisabled();
ImGui.Columns(4);
ImGui.NextColumn(); ImGui.BeginDisabled(!model.HasVertexColors); ImGui.Checkbox("Colors", ref model.bVertexColors); ImGui.EndDisabled();
ImGui.NextColumn(); ImGui.Checkbox("Normals", ref model.bVertexNormals);
ImGui.NextColumn(); ImGui.Checkbox("Tangent", ref model.bVertexTangent);
ImGui.NextColumn(); ImGui.Checkbox("Coords", ref model.bVertexTexCoords);
ImGui.Columns(1);
ImGui.Separator();
if (ImGui.BeginTabBar("tabbar_details", ImGuiTabBarFlags.None))
{
if (ImGui.BeginTabItem("Sections") && ImGui.BeginTable("table_sections", 2, ImGuiTableFlags.Borders | ImGuiTableFlags.Resizable))
{
ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed);
ImGui.TableSetupColumn("Material");
ImGui.TableHeadersRow();
var i = 0;
foreach ((FGuid guid, Model model) in s.Renderer.Cache.Models)
var swap = false;
for (var i = 0; i < model.Sections.Length; i++)
{
var section = model.Sections[i];
var material = model.Materials[section.MaterialIndex];
ImGui.PushID(i);
ImGui.TableNextRow();
ImGui.TableNextColumn();
if (!model.Show)
if (!section.Show)
{
ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, ImGui.GetColorU32(new Vector4(1, 0, 0, .5f)));
}
ImGui.Text(model.TransformsCount.ToString("D"));
ImGui.Text(section.MaterialIndex.ToString("D"));
ImGui.TableNextColumn();
ImGui.Text(model.NumTexCoords.ToString("D"));
ImGui.TableNextColumn();
model.IsSelected = s.Renderer.Settings.SelectedModel == guid;
if (ImGui.Selectable(model.Name, model.IsSelected, ImGuiSelectableFlags.SpanAllColumns))
if (ImGui.Selectable(material.Name, s.Renderer.Settings.SelectedSection == i, ImGuiSelectableFlags.SpanAllColumns))
{
s.Renderer.Settings.SelectModel(guid);
s.Renderer.Settings.SelectSection(i);
}
if (ImGui.BeginPopupContextItem())
{
s.Renderer.Settings.SelectModel(guid);
if (ImGui.Selectable("Delete")) s.Renderer.Cache.Models.Remove(guid);
if (ImGui.Selectable("Deselect")) s.Renderer.Settings.SelectModel(Guid.Empty);
if (ImGui.Selectable("Copy Name to Clipboard")) ImGui.SetClipboardText(model.Name);
ImGui.EndPopup();
}
ImGui.PopID();
i++;
}
ImGui.EndTable();
}
PopStyleCompact();
ImGui.End();
}
}
private void DrawDetails(Snooper s)
{
if (ImGui.Begin("Details"))
{
PushStyleCompact();
var guid = s.Renderer.Settings.SelectedModel;
if (s.Renderer.Cache.Models.TryGetValue(guid, out var model))
{
ImGui.Text($"Entity: ({model.Type}) {model.Name}");
ImGui.Text($"Guid: {guid.ToString(EGuidFormats.UniqueObjectGuid)}");
ImGui.Columns(3, "Actions", false);
if (ImGui.Button("Go To"))
{
var instancePos = model.Transforms[model.SelectedInstance].Position;
s.Camera.Position = new Vector3(instancePos.X, instancePos.Y, instancePos.Z);
}
ImGui.NextColumn(); ImGui.Checkbox("Show", ref model.Show);
ImGui.NextColumn(); ImGui.BeginDisabled(!model.Show); ImGui.Checkbox("Wire", ref model.Wireframe); ImGui.EndDisabled();
ImGui.Columns(4);
ImGui.NextColumn(); ImGui.BeginDisabled(!model.HasVertexColors); ImGui.Checkbox("Colors", ref model.bVertexColors); ImGui.EndDisabled();
ImGui.NextColumn(); ImGui.Checkbox("Normals", ref model.bVertexNormals);
ImGui.NextColumn(); ImGui.Checkbox("Tangent", ref model.bVertexTangent);
ImGui.NextColumn(); ImGui.Checkbox("Coords", ref model.bVertexTexCoords);
ImGui.Columns(1);
ImGui.Separator();
if (ImGui.BeginTabBar("tabbar_details", ImGuiTabBarFlags.None))
{
if (ImGui.BeginTabItem("Sections") && ImGui.BeginTable("table_sections", 2, ImGuiTableFlags.Borders | ImGuiTableFlags.Resizable))
{
ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed);
ImGui.TableSetupColumn("Material");
ImGui.TableHeadersRow();
var swap = false;
for (var i = 0; i < model.Sections.Length; i++)
s.Renderer.Settings.SelectSection(i);
if (ImGui.Selectable("Swap"))
{
var section = model.Sections[i];
var material = model.Materials[section.MaterialIndex];
ImGui.PushID(i);
ImGui.TableNextRow();
ImGui.TableNextColumn();
if (!section.Show)
if (_swapAwareness)
{
ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, ImGui.GetColorU32(new Vector4(1, 0, 0, .5f)));
}
ImGui.Text(section.MaterialIndex.ToString("D"));
ImGui.TableNextColumn();
if (ImGui.Selectable(material.Name, s.Renderer.Settings.SelectedSection == i, ImGuiSelectableFlags.SpanAllColumns))
{
s.Renderer.Settings.SelectSection(i);
}
if (ImGui.BeginPopupContextItem())
{
s.Renderer.Settings.SelectSection(i);
if (ImGui.Selectable("Swap"))
{
if (_swapAwareness)
{
s.Renderer.Settings.SwapMaterial(true);
s.WindowShouldClose(true, false);
}
else swap = true;
}
if (ImGui.Selectable("Copy Name to Clipboard")) ImGui.SetClipboardText(material.Name);
ImGui.EndPopup();
}
ImGui.PopID();
}
ImGui.EndTable();
var p_open = true;
if (swap) ImGui.OpenPopup("Swap?");
ImGui.SetNextWindowPos(ImGui.GetMainViewport().GetCenter(), ImGuiCond.Appearing, new Vector2(.5f));
if (ImGui.BeginPopupModal("Swap?", ref p_open, ImGuiWindowFlags.AlwaysAutoResize))
{
ImGui.TextWrapped("You're about to swap a material.\nThe window will close for you to extract a material!\n\n");
ImGui.Separator();
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero);
ImGui.Checkbox("Got it! Don't show me again", ref _swapAwareness);
ImGui.PopStyleVar();
var size = new Vector2(120, 0);
if (ImGui.Button("OK", size))
{
ImGui.CloseCurrentPopup();
s.Renderer.Settings.SwapMaterial(true);
s.WindowShouldClose(true, false);
}
ImGui.SetItemDefaultFocus();
ImGui.SameLine();
if (ImGui.Button("Cancel", size))
{
ImGui.CloseCurrentPopup();
}
ImGui.EndPopup();
else swap = true;
}
if (ImGui.Selectable("Copy Name to Clipboard")) ImGui.SetClipboardText(material.Name);
ImGui.EndPopup();
}
ImGui.PopID();
}
ImGui.EndTable();
ImGui.EndTabItem();
var p_open = true;
if (swap) ImGui.OpenPopup("Swap?");
ImGui.SetNextWindowPos(ImGui.GetMainViewport().GetCenter(), ImGuiCond.Appearing, new Vector2(.5f));
if (ImGui.BeginPopupModal("Swap?", ref p_open, ImGuiWindowFlags.AlwaysAutoResize))
{
ImGui.TextWrapped("You're about to swap a material.\nThe window will close for you to extract a material!\n\n");
ImGui.Separator();
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero);
ImGui.Checkbox("Got it! Don't show me again", ref _swapAwareness);
ImGui.PopStyleVar();
var size = new Vector2(120, 0);
if (ImGui.Button("OK", size))
{
ImGui.CloseCurrentPopup();
s.Renderer.Settings.SwapMaterial(true);
s.WindowShouldClose(true, false);
}
if (ImGui.BeginTabItem("Morph Targets"))
ImGui.SetItemDefaultFocus();
ImGui.SameLine();
if (ImGui.Button("Cancel", size))
{
if (model.HasMorphTargets)
{
const float width = 10;
var region = ImGui.GetContentRegionAvail();
var box = new Vector2(region.X - width, region.Y / 1.5f);
ImGui.CloseCurrentPopup();
}
if (ImGui.BeginListBox("", box))
ImGui.EndPopup();
}
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem("Morph Targets"))
{
if (model.HasMorphTargets)
{
const float width = 10;
var region = ImGui.GetContentRegionAvail();
var box = new Vector2(region.X - width, region.Y / 1.5f);
if (ImGui.BeginListBox("", box))
{
for (int i = 0; i < model.Morphs.Length; i++)
{
ImGui.PushID(i);
if (ImGui.Selectable(model.Morphs[i].Name, s.Renderer.Settings.SelectedMorph == i))
{
for (int i = 0; i < model.Morphs.Length; i++)
{
ImGui.PushID(i);
if (ImGui.Selectable(model.Morphs[i].Name, s.Renderer.Settings.SelectedMorph == i))
{
s.Renderer.Settings.SelectMorph(i, model);
}
ImGui.PopID();
}
ImGui.EndListBox();
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.PopID(); ImGui.PopStyleVar();
ImGui.Spacing();
ImGui.Text($"Time: {model.MorphTime:P}%");
s.Renderer.Settings.SelectMorph(i, model);
}
ImGui.PopID();
}
else
{
ImGui.TextColored(_errorColor, "mesh has no morph targets");
}
ImGui.EndListBox();
ImGui.EndTabItem();
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.PopID(); ImGui.PopStyleVar();
ImGui.Spacing();
ImGui.Text($"Time: {model.MorphTime:P}%");
}
}
else
{
ImGui.TextColored(_errorColor, "mesh has no morph targets");
}
ImGui.EndTabItem();
}
else NoMeshSelected();
PopStyleCompact();
ImGui.End();
}
}
private void DrawTransform(Snooper s)
private void DrawTransform(Model model, float speed)
{
if (ImGui.Begin("Transform"))
const int width = 100;
ImGui.PushID(0); ImGui.BeginDisabled(model.TransformsCount < 2);
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
ImGui.SliderInt("", ref model.SelectedInstance, 0, model.TransformsCount - 1, "Instance %i", ImGuiSliderFlags.AlwaysClamp);
ImGui.EndDisabled(); ImGui.PopID();
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
if (ImGui.TreeNode("Location"))
{
PushStyleCompact();
if (s.Renderer.Cache.Models.TryGetValue(s.Renderer.Settings.SelectedModel, out var model))
ImGui.PushID(1);
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("X", ref model.Transforms[model.SelectedInstance].Position.X, speed, 0f, 0f, "%.2f m");
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("Y", ref model.Transforms[model.SelectedInstance].Position.Z, speed, 0f, 0f, "%.2f m");
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("Z", ref model.Transforms[model.SelectedInstance].Position.Y, speed, 0f, 0f, "%.2f m");
ImGui.PopID();
ImGui.TreePop();
}
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
if (ImGui.TreeNode("Rotation"))
{
ImGui.PushID(2);
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("X", ref model.Transforms[model.SelectedInstance].Rotation.Roll, .5f, 0f, 0f, "%.1f°");
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("Y", ref model.Transforms[model.SelectedInstance].Rotation.Pitch, .5f, 0f, 0f, "%.1f°");
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("Z", ref model.Transforms[model.SelectedInstance].Rotation.Yaw, .5f, 0f, 0f, "%.1f°");
ImGui.PopID();
ImGui.TreePop();
}
if (ImGui.TreeNode("Scale"))
{
ImGui.PushID(3);
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("X", ref model.Transforms[model.SelectedInstance].Scale.X, speed, 0f, 0f, "%.3f");
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("Y", ref model.Transforms[model.SelectedInstance].Scale.Z, speed, 0f, 0f, "%.3f");
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("Z", ref model.Transforms[model.SelectedInstance].Scale.Y, speed, 0f, 0f, "%.3f");
ImGui.PopID();
ImGui.TreePop();
}
model.UpdateMatrix(model.SelectedInstance);
}
private void DrawUvChannels(Model model, Section section)
{
var width = ImGui.GetContentRegionAvail().X;
var material = model.Materials[section.MaterialIndex];
ImGui.PushID(0); ImGui.BeginDisabled(model.NumTexCoords < 2);
ImGui.SetNextItemWidth(width);
ImGui.SliderInt("", ref material.SelectedChannel, 0, model.NumTexCoords - 1, "Channel %i", ImGuiSliderFlags.AlwaysClamp);
ImGui.EndDisabled(); ImGui.PopID();
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
if (ImGui.TreeNode("Textures"))
{
if (material.Diffuse.Length > 0)
{
const int width = 100;
var speed = s.Camera.Speed / 100;
ImGui.PushID(0); ImGui.BeginDisabled(model.TransformsCount < 2);
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
ImGui.SliderInt("", ref model.SelectedInstance, 0, model.TransformsCount - 1, "Instance %i", ImGuiSliderFlags.AlwaysClamp);
ImGui.EndDisabled(); ImGui.PopID();
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
if (ImGui.TreeNode("Location"))
{
ImGui.PushID(1);
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("X", ref model.Transforms[model.SelectedInstance].Position.X, speed, 0f, 0f, "%.2f m");
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("Y", ref model.Transforms[model.SelectedInstance].Position.Z, speed, 0f, 0f, "%.2f m");
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("Z", ref model.Transforms[model.SelectedInstance].Position.Y, speed, 0f, 0f, "%.2f m");
ImGui.PopID();
ImGui.TreePop();
}
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
if (ImGui.TreeNode("Rotation"))
{
ImGui.PushID(2);
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("X", ref model.Transforms[model.SelectedInstance].Rotation.Roll, .5f, 0f, 0f, "%.1f°");
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("Y", ref model.Transforms[model.SelectedInstance].Rotation.Pitch, .5f, 0f, 0f, "%.1f°");
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("Z", ref model.Transforms[model.SelectedInstance].Rotation.Yaw, .5f, 0f, 0f, "%.1f°");
ImGui.PopID();
ImGui.TreePop();
}
if (ImGui.TreeNode("Scale"))
{
ImGui.PushID(3);
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("X", ref model.Transforms[model.SelectedInstance].Scale.X, speed, 0f, 0f, "%.3f");
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("Y", ref model.Transforms[model.SelectedInstance].Scale.Z, speed, 0f, 0f, "%.3f");
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("Z", ref model.Transforms[model.SelectedInstance].Scale.Y, speed, 0f, 0f, "%.3f");
ImGui.PopID();
ImGui.TreePop();
}
model.UpdateMatrix(model.SelectedInstance);
var size = new Vector2(ImGui.GetContentRegionAvail().X / 5.75f);
DrawSquareTexture(material.Diffuse[material.SelectedChannel], size); ImGui.SameLine();
DrawSquareTexture(material.Normals[material.SelectedChannel], size); ImGui.SameLine();
DrawSquareTexture(material.SpecularMasks[material.SelectedChannel], size); ImGui.SameLine();
DrawSquareTexture(material.M.Texture, size); ImGui.SameLine();
DrawSquareTexture(material.Emissive[material.SelectedChannel], size); ImGui.SameLine();
}
else NoMeshSelected();
PopStyleCompact();
ImGui.End();
else TextColored(_errorColor, "no texture in material section");
}
}
private void DrawUvChannels(Snooper s)
private void DrawMaterials(Model model, Section section)
{
if (ImGui.Begin("UV Channels"))
var material = model.Materials[section.MaterialIndex];
foreach ((string key, float value) in material.Parameters.Scalars)
{
PushStyleCompact();
if (s.Renderer.Cache.Models.TryGetValue(s.Renderer.Settings.SelectedModel, out var model) &&
s.Renderer.Settings.TryGetSection(model, out var section))
{
var width = ImGui.GetContentRegionAvail().X;
var material = model.Materials[section.MaterialIndex];
ImGui.Text($"{key}: {value}");
}
ImGui.Spacing(); ImGui.Separator(); ImGui.Spacing();
foreach ((string key, FLinearColor value) in material.Parameters.Colors)
{
ImGui.Text(key); ImGui.SameLine();
ImGui.ColorButton(key, new Vector4(value.R, value.G, value.B, value.A));
}
}
ImGui.PushID(0); ImGui.BeginDisabled(model.NumTexCoords < 2);
ImGui.SetNextItemWidth(width);
ImGui.SliderInt("", ref material.SelectedChannel, 0, model.NumTexCoords - 1, "Channel %i", ImGuiSliderFlags.AlwaysClamp);
ImGui.EndDisabled(); ImGui.PopID();
private void DrawTextures(Model model, Section section)
{
var material = model.Materials[section.MaterialIndex];
foreach ((string key, UUnrealMaterial value) in material.Parameters.Textures)
{
ImGui.Text($"{key}: {value.Name}");
}
}
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
if (ImGui.TreeNode("Textures"))
{
if (material.Diffuse.Length > 0)
{
var size = new Vector2(ImGui.GetContentRegionAvail().X / 5.75f);
DrawSquareTexture(material.Diffuse[material.SelectedChannel], size); ImGui.SameLine();
DrawSquareTexture(material.Normals[material.SelectedChannel], size); ImGui.SameLine();
DrawSquareTexture(material.SpecularMasks[material.SelectedChannel], size); ImGui.SameLine();
DrawSquareTexture(material.M.Texture, size); ImGui.SameLine();
DrawSquareTexture(material.Emissive[material.SelectedChannel], size); ImGui.SameLine();
}
else TextColored(_errorColor, "no texture in material section");
}
}
else NoMeshSelected();
PopStyleCompact();
ImGui.End();
private void DrawParameters(Model model, Section section)
{
const int width = 50;
var material = model.Materials[section.MaterialIndex];
ImGui.Checkbox("Show Section", ref section.Show);
ImGui.SetNextItemWidth(width); ImGui.DragFloat("Emissive Multiplier", ref material.EmissiveMult, .01f, 0f);
ImGui.SetNextItemWidth(width); ImGui.DragFloat("UV Scale", ref material.UVScale, .01f, 0f);
if (material.HasM)
{
ImGui.ColorEdit3("Skin Boost Color", ref material.M.SkinBoost.Color, ImGuiColorEditFlags.NoInputs);
ImGui.SetNextItemWidth(width); ImGui.DragFloat("Skin Boost Exponent", ref material.M.SkinBoost.Exponent, .01f, 0f);
ImGui.SetNextItemWidth(width); ImGui.DragFloat("AmbientOcclusion", ref material.M.AmbientOcclusion, .01f, 0f, 1f);
ImGui.SetNextItemWidth(width); ImGui.DragFloat("Cavity", ref material.M.Cavity, .01f, 0f, 1f);
}
}
private void Draw3DViewport(Snooper s)
{
const float lookSensitivity = 0.1f;
const ImGuiWindowFlags flags =
ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse |
ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.AlwaysUseWindowPadding;
// ImGui.SetNextWindowSize(_viewportSize, _firstUse);
// ImGui.SetNextWindowPos(_viewportPosition, _firstUse);
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
ImGui.Begin("3D Viewport", flags);
Window("3D Viewport", () =>
{
var largest = ImGui.GetContentRegionAvail();
largest.X -= ImGui.GetScrollX();
largest.Y -= ImGui.GetScrollY();
var size = new Vector2(largest.X, largest.Y);
s.Camera.AspectRatio = size.X / size.Y;
ImGui.ImageButton(s.Framebuffer.GetPointer(), size, new Vector2(0, 1), new Vector2(1, 0), 0);
if (ImGui.IsItemHovered())
{
// if left button down while mouse is hover viewport
if (ImGui.IsMouseDown(ImGuiMouseButton.Left) && !_viewportFocus)
{
_viewportFocus = true;
s.CursorState = CursorState.Grabbed;
}
if (ImGui.IsMouseClicked(ImGuiMouseButton.Right))
{
var guid = s.Renderer.Picking.ReadPixel(ImGui.GetMousePos(), ImGui.GetCursorScreenPos(), size);
s.Renderer.Settings.SelectModel(guid);
ImGui.SetWindowFocus("Outliner");
}
}
const float lookSensitivity = 0.1f;
if (ImGui.IsMouseDragging(ImGuiMouseButton.Left, lookSensitivity) && _viewportFocus)
{
var io = ImGui.GetIO();
var delta = io.MouseDelta * lookSensitivity;
s.Camera.ModifyDirection(delta.X, delta.Y);
}
// if left button up and mouse was in viewport
if (ImGui.IsMouseReleased(ImGuiMouseButton.Left) && _viewportFocus)
{
_viewportFocus = false;
s.CursorState = CursorState.Normal;
}
float framerate = ImGui.GetIO().Framerate;
ImGui.SetCursorPos(size with { X = 7.5f });
ImGui.Text($"FPS: {framerate:0} ({1000.0f / framerate:0.##} ms)");
}, false);
ImGui.PopStyleVar();
}
var largest = ImGui.GetContentRegionAvail();
largest.X -= ImGui.GetScrollX();
largest.Y -= ImGui.GetScrollY();
var size = new Vector2(largest.X, largest.Y);
s.Camera.AspectRatio = size.X / size.Y;
ImGui.ImageButton(s.Framebuffer.GetPointer(), size, new Vector2(0, 1), new Vector2(1, 0), 0);
// it took me 5 hours to make it work, don't change any of the following code
// basically the Raw cursor doesn't actually freeze the mouse position
// so for ImGui, the IsItemHovered will be false if mouse leave, even in Raw mode
if (ImGui.IsItemHovered())
private void Window(string name, Action content, bool styled = true)
{
if (ImGui.Begin(name))
{
// if left button down while mouse is hover viewport
if (ImGui.IsMouseDown(ImGuiMouseButton.Left) && !_viewportFocus)
{
_viewportFocus = true;
s.CursorState = CursorState.Grabbed;
}
if (ImGui.IsMouseClicked(ImGuiMouseButton.Right))
{
var guid = s.Renderer.Picking.ReadPixel(ImGui.GetMousePos(), ImGui.GetCursorScreenPos(), size);
s.Renderer.Settings.SelectModel(guid);
ImGui.SetWindowFocus("Outliner");
}
if (styled) PushStyleCompact();
content();
if (styled) PopStyleCompact();
ImGui.End();
}
}
// this can't be inside IsItemHovered! read it as
// if left mouse button was pressed while hovering the viewport
// move camera until left mouse button is released
// no matter where mouse position end up
if (ImGui.IsMouseDragging(ImGuiMouseButton.Left, lookSensitivity) && _viewportFocus)
private void MeshWindow(string name, Renderer renderer, Action<Model> content, bool styled = true)
{
Window(name, () =>
{
var io = ImGui.GetIO();
var delta = io.MouseDelta * lookSensitivity;
s.Camera.ModifyDirection(delta.X, delta.Y);
}
if (renderer.Cache.Models.TryGetValue(renderer.Settings.SelectedModel, out var model)) content(model);
else NoMeshSelected();
}, styled);
}
// if left button up and mouse was in viewport
if (ImGui.IsMouseReleased(ImGuiMouseButton.Left) && _viewportFocus)
private void SectionWindow(string name, Renderer renderer, Action<Model, Section> content, bool styled = true)
{
MeshWindow(name, renderer, model =>
{
_viewportFocus = false;
s.CursorState = CursorState.Normal;
}
const float padding = 7.5f;
float framerate = ImGui.GetIO().Framerate;
var text = $"FPS: {framerate:0} ({1000.0f / framerate:0.##} ms)";
ImGui.SetCursorPos(size with { X = padding });
ImGui.Text(text);
ImGui.End();
if (renderer.Settings.TryGetSection(model, out var section)) content(model, section);
else NoSectionSelected();
}, styled);
}
private void PopStyleCompact() => ImGui.PopStyleVar(2);
@ -561,6 +549,7 @@ public class SnimGui
}
private void NoMeshSelected() => TextColored(_errorColor, "no mesh selected");
private void NoSectionSelected() => TextColored(_errorColor, "no section selected");
private void TextColored(Vector4 color, string text)
{
ImGui.TextColored(color, text);

View File

@ -0,0 +1,26 @@
using System;
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
namespace FModel.Views.Snooper;
public class Socket : IDisposable
{
public readonly string Name;
public readonly string Bone;
public readonly Transform Transform;
public Socket(USkeletalMeshSocket socket, Transform relation)
{
Name = socket.SocketName.Text;
Bone = socket.BoneName.Text;
Transform = Transform.Identity;
Transform.Relation = relation.Matrix;
Transform.Position = socket.RelativeLocation.ToMapVector() * Constants.SCALE_DOWN_RATIO;
Transform.Rotation = socket.RelativeRotation;
}
public void Dispose()
{
throw new NotImplementedException();
}
}