added a guizmo for model transformation

This commit is contained in:
Asval 2025-03-28 19:19:50 +01:00
parent 0e4d0431a3
commit e8b061f018
9 changed files with 171 additions and 139 deletions

@ -1 +1 @@
Subproject commit 24c816dfd462a95e087344419486a8f8008f77f5
Subproject commit 4514f1acd51c9d3458eb0eda0abf37bb77453d2b

View File

@ -156,7 +156,6 @@
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
<PackageReference Include="EpicManifestParser" Version="2.4.1" />
<PackageReference Include="EpicManifestParser.ZlibngDotNetDecompressor" Version="1.0.1" />
<PackageReference Include="ImGui.NET" Version="1.91.0.1" />
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.3.8" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NVorbis" Version="0.10.5" />
@ -168,6 +167,7 @@
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.6" />
<PackageReference Include="SkiaSharp.HarfBuzz" Version="2.88.9" />
<PackageReference Include="SkiaSharp.Svg" Version="1.60.0" />
<PackageReference Include="Twizzle.ImGui-Bundle.NET" Version="1.91.5.2" />
</ItemGroup>
<ItemGroup>

View File

@ -3,12 +3,12 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Forms;
using FModel.Settings;
using ImGuiNET;
using ImGuizmoNET;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Windowing.Desktop;
using OpenTK.Windowing.GraphicsLibraryFramework;
@ -54,11 +54,12 @@ public class ImGuiController : IDisposable
int major = GL.GetInteger(GetPName.MajorVersion);
int minor = GL.GetInteger(GetPName.MinorVersion);
KHRDebugAvailable = (major == 4 && minor >= 3) || IsExtensionSupported("KHR_debug");
IntPtr context = ImGui.CreateContext();
ImGui.SetCurrentContext(context);
IntPtr imguiCtx = ImGui.CreateContext();
ImGui.SetCurrentContext(imguiCtx);
var io = ImGui.GetIO();
unsafe
@ -69,32 +70,24 @@ public class ImGuiController : IDisposable
FontNormal = io.Fonts.AddFontFromFileTTF("C:\\Windows\\Fonts\\segoeui.ttf", 16 * DpiScale);
FontBold = io.Fonts.AddFontFromFileTTF("C:\\Windows\\Fonts\\segoeuib.ttf", 16 * DpiScale);
FontSemiBold = io.Fonts.AddFontFromFileTTF("C:\\Windows\\Fonts\\seguisb.ttf", 16 * DpiScale);
io.Fonts.AddFontDefault();
io.Fonts.Build(); // Build font atlas
io.BackendFlags |= ImGuiBackendFlags.RendererHasVtxOffset;
io.ConfigFlags |= ImGuiConfigFlags.NavEnableKeyboard;
io.ConfigFlags |= ImGuiConfigFlags.DockingEnable;
io.ConfigFlags |= ImGuiConfigFlags.ViewportsEnable;
io.Fonts.Flags |= ImFontAtlasFlags.NoBakedLines;
// io.ConfigDockingWithShift = true;
io.ConfigDockingWithShift = true;
io.ConfigWindowsMoveFromTitleBarOnly = true;
io.BackendRendererUserData = 0;
CreateDeviceResources();
SetPerFrameImGuiData(1f / 60f);
ImGui.NewFrame();
_frameBegun = true;
}
public void Normal() => PushFont(FontNormal);
public void Bold() => PushFont(FontBold);
public void SemiBold() => PushFont(FontSemiBold);
public void PopFont()
{
ImGui.PopFont();
PushFont(FontNormal);
}
private void PushFont(ImFontPtr ptr) => ImGui.PushFont(ptr);
public void WindowResized(int width, int height)
@ -132,34 +125,42 @@ public class ImGuiController : IDisposable
RecreateFontDeviceTexture();
string VertexSource = @"#version 460 core
string VertexSource = @"#version 330 core
uniform mat4 projection_matrix;
layout(location = 0) in vec2 in_position;
layout(location = 1) in vec2 in_texCoord;
layout(location = 2) in vec4 in_color;
out vec4 color;
out vec2 texCoord;
void main()
{
gl_Position = projection_matrix * vec4(in_position, 0, 1);
color = in_color;
texCoord = in_texCoord;
gl_Position = projection_matrix * vec4(in_position, 0, 1);
color = in_color;
texCoord = in_texCoord;
}";
string FragmentSource = @"#version 460 core
string FragmentSource = @"#version 330 core
uniform sampler2D in_fontTexture;
in vec4 color;
in vec2 texCoord;
out vec4 outputColor;
void main()
{
outputColor = color * texture(in_fontTexture, texCoord);
outputColor = color * texture(in_fontTexture, texCoord);
}";
_shader = CreateProgram("ImGui", VertexSource, FragmentSource);
_shaderProjectionMatrixLocation = GL.GetUniformLocation(_shader, "projection_matrix");
_shaderFontTextureLocation = GL.GetUniformLocation(_shader, "in_fontTexture");
int stride = Unsafe.SizeOf<ImDrawVert>();
int stride = Marshal.SizeOf<ImDrawVert>();
GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, stride, 0);
GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, stride, 8);
GL.VertexAttribPointer(2, 4, VertexAttribPointerType.UnsignedByte, true, stride, 16);
@ -225,7 +226,6 @@ outputColor = color * texture(in_fontTexture, texCoord);
ImGui.Render();
RenderImDrawData(ImGui.GetDrawData());
}
CheckGLError("End of frame");
}
/// <summary>
@ -243,6 +243,7 @@ outputColor = color * texture(in_fontTexture, texCoord);
_frameBegun = true;
ImGui.NewFrame();
ImGuizmo.BeginFrame();
}
/// <summary>
@ -252,7 +253,6 @@ outputColor = color * texture(in_fontTexture, texCoord);
private void SetPerFrameImGuiData(float deltaSeconds)
{
ImGuiIOPtr io = ImGui.GetIO();
// if (io.WantSaveIniSettings) ImGui.SaveIniSettingsToDisk(_iniPath);
io.DisplaySize = new Vector2(
_windowWidth / _scaleFactor.X,
_windowHeight / _scaleFactor.Y);
@ -265,6 +265,7 @@ outputColor = color * texture(in_fontTexture, texCoord);
private void UpdateImGuiInput(GameWindow wnd)
{
ImGuiIOPtr io = ImGui.GetIO();
var mState = wnd.MouseState;
var kState = wnd.KeyboardState;
@ -276,7 +277,7 @@ outputColor = color * texture(in_fontTexture, texCoord);
io.AddMouseButtonEvent(4, mState[MouseButton.Button2]);
io.AddMouseWheelEvent(mState.ScrollDelta.X, mState.ScrollDelta.Y);
foreach (Keys key in Enum.GetValues(typeof(Keys)))
foreach (Keys key in Enum.GetValues<Keys>())
{
if (key == Keys.Unknown) continue;
io.AddKeyEvent(TranslateKey(key), kState.IsKeyDown(key));
@ -294,7 +295,7 @@ outputColor = color * texture(in_fontTexture, texCoord);
io.KeySuper = kState.IsKeyDown(Keys.LeftSuper) || kState.IsKeyDown(Keys.RightSuper);
}
public void PressChar(char keyChar)
internal void PressChar(char keyChar)
{
PressedChars.Add(keyChar);
}
@ -340,7 +341,7 @@ outputColor = color * texture(in_fontTexture, texCoord);
{
ImDrawListPtr cmd_list = draw_data.CmdLists[i];
int vertexSize = cmd_list.VtxBuffer.Size * Unsafe.SizeOf<ImDrawVert>();
int vertexSize = cmd_list.VtxBuffer.Size * Marshal.SizeOf<ImDrawVert>();
if (vertexSize > _vertexBufferSize)
{
int newSize = (int)Math.Max(_vertexBufferSize * 1.5f, vertexSize);
@ -390,7 +391,7 @@ outputColor = color * texture(in_fontTexture, texCoord);
{
ImDrawListPtr cmd_list = draw_data.CmdLists[n];
GL.BufferSubData(BufferTarget.ArrayBuffer, IntPtr.Zero, cmd_list.VtxBuffer.Size * Unsafe.SizeOf<ImDrawVert>(), cmd_list.VtxBuffer.Data);
GL.BufferSubData(BufferTarget.ArrayBuffer, IntPtr.Zero, cmd_list.VtxBuffer.Size * Marshal.SizeOf<ImDrawVert>(), cmd_list.VtxBuffer.Data);
CheckGLError($"Data Vert {n}");
GL.BufferSubData(BufferTarget.ElementArrayBuffer, IntPtr.Zero, cmd_list.IdxBuffer.Size * sizeof(ushort), cmd_list.IdxBuffer.Data);
@ -544,16 +545,16 @@ outputColor = color * texture(in_fontTexture, texCoord);
public static ImGuiKey TranslateKey(Keys key)
{
if (key is >= Keys.D0 and <= Keys.D9)
if (key >= Keys.D0 && key <= Keys.D9)
return key - Keys.D0 + ImGuiKey._0;
if (key is >= Keys.A and <= Keys.Z)
if (key >= Keys.A && key <= Keys.Z)
return key - Keys.A + ImGuiKey.A;
if (key is >= Keys.KeyPad0 and <= Keys.KeyPad9)
if (key >= Keys.KeyPad0 && key <= Keys.KeyPad9)
return key - Keys.KeyPad0 + ImGuiKey.Keypad0;
if (key is >= Keys.F1 and <= Keys.F24)
if (key >= Keys.F1 && key <= Keys.F24)
return key - Keys.F1 + ImGuiKey.F24;
return key switch
@ -596,7 +597,7 @@ outputColor = color * texture(in_fontTexture, texCoord);
Keys.KeyPadAdd => ImGuiKey.KeypadAdd,
Keys.KeyPadEnter => ImGuiKey.KeypadEnter,
Keys.KeyPadEqual => ImGuiKey.KeypadEqual,
Keys.LeftShift => ImGuiKey.LeftShift,
Keys.LeftShift => ImGuiKey.ModShift,
Keys.LeftControl => ImGuiKey.LeftCtrl,
Keys.LeftAlt => ImGuiKey.LeftAlt,
Keys.LeftSuper => ImGuiKey.LeftSuper,

View File

@ -390,6 +390,12 @@ public class CUE4ParseViewModel : ViewModel
FLogger.Text("Additive animations have their reference pose stripped, which will lead to inaccurate preview and export", Constants.WHITE, true));
}
if (Provider.Versions.Game is EGame.GAME_UE4_LATEST or EGame.GAME_UE5_LATEST && !Provider.ProjectName.Equals("FortniteGame", StringComparison.OrdinalIgnoreCase)) // ignore fortnite globally
{
FLogger.Append(ELog.Warning, () =>
FLogger.Text($"Experimental UE version selected, likely unsuitable for '{Provider.GameDisplayName ?? Provider.ProjectName}'", Constants.WHITE, true));
}
return Task.CompletedTask;
}
@ -578,7 +584,7 @@ public class CUE4ParseViewModel : ViewModel
case "manifest":
case "uplugin":
case "archive":
case "vmodule":
case "vmodule":
case "uparam": // Steel Hunters
case "verse":
case "html":

View File

@ -174,6 +174,7 @@ public class SkeletalModel : UModel
public void Render(Shader shader)
{
shader.SetUniform("uMorphTime", MorphTime);
shader.SetUniform("uIsSpline", false);
Skeleton.Render(shader);
}

View File

@ -373,6 +373,7 @@ public abstract class UModel : IRenderableModel
MatrixVbo = new BufferObject<Matrix4x4>(TransformsCount, BufferTarget.ArrayBuffer);
for (int instance = 0; instance < TransformsCount; instance++)
{
Transforms[instance].Save();
MatrixVbo.Update(instance, Transforms[instance].Matrix);
}
Vao.BindInstancing(); // VertexAttributePointer

View File

@ -343,7 +343,7 @@ public class Material : IDisposable
var texture = GetSelectedTexture() ?? icons["noimage"];
ImGui.Image(texture.GetPointer(),
new Vector2(ImGui.GetContentRegionAvail().X - ImGui.GetScrollX()),
Vector2.Zero, Vector2.One, Vector4.One, new Vector4(1.0f, 1.0f, 1.0f, 0.25f));
Vector2.Zero, Vector2.One);
return ImGui.IsItemHovered() && ImGui.IsMouseDoubleClicked(ImGuiMouseButton.Left);
}

View File

@ -11,6 +11,7 @@ using FModel.Settings;
using FModel.Views.Snooper.Animations;
using FModel.Views.Snooper.Models;
using FModel.Views.Snooper.Shading;
using ImGuizmoNET;
using OpenTK.Graphics.OpenGL4;
namespace FModel.Views.Snooper;
@ -67,7 +68,9 @@ public class SnimGui
private Vector2 _outlinerSize;
private bool _tiOpen;
private bool _transformOpen;
private bool _viewportFocus;
private OPERATION _guizmoOperation;
private readonly Vector4 _accentColor = new (0.125f, 0.42f, 0.831f, 1.0f);
private readonly Vector4 _alertColor = new (0.831f, 0.573f, 0.125f, 1.0f);
@ -82,14 +85,14 @@ public class SnimGui
_renderer = GL.GetString(StringName.Renderer);
_version = "OpenGL " + GL.GetString(StringName.Version);
_tableWidth = 17 * Controller.DpiScale;
_guizmoOperation = OPERATION.TRANSLATE;
Theme();
}
public void Render(Snooper s)
{
Controller.SemiBold();
DrawDockSpace(s.ClientSize);
ImGui.DockSpaceOverViewport(_dockspaceId, ImGui.GetMainViewport(), ImGuiDockNodeFlags.PassthruCentralNode);
SectionWindow("Material Inspector", s.Renderer, DrawMaterialInspector, false);
AnimationWindow("Timeline", s.Renderer, (icons, tracker, animations) =>
@ -249,22 +252,6 @@ public class SnimGui
}
}
private void DrawDockSpace(OpenTK.Mathematics.Vector2i size)
{
const ImGuiWindowFlags flags =
ImGuiWindowFlags.MenuBar | ImGuiWindowFlags.NoDocking |
ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoResize |
ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoMove |
ImGuiWindowFlags.NoBringToFrontOnFocus | ImGuiWindowFlags.NoNavFocus;
ImGui.SetNextWindowPos(new Vector2(0, 0));
ImGui.SetNextWindowSize(new Vector2(size.X, size.Y));
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
ImGui.Begin("Oui oui", flags);
ImGui.PopStyleVar();
ImGui.DockSpace(_dockspaceId);
}
private void DrawNavbar()
{
if (!ImGui.BeginMainMenuBar()) return;
@ -604,14 +591,43 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem("Transform"))
_transformOpen = ImGui.BeginTabItem("Transform");
if (_transformOpen)
{
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();
model.Transforms[model.SelectedInstance].ImGuiTransform(s.Renderer.CameraOp.Speed / 100f);
if (ImGui.BeginTable("guizmo_controls", 2, ImGuiTableFlags.SizingStretchProp))
{
var t = model.Transforms[model.SelectedInstance];
var c = _guizmoOperation switch
{
OPERATION.TRANSLATE => 0,
OPERATION.ROTATE => 1,
OPERATION.SCALE => 2,
_ => 3
};
Layout("Operation ");
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X * 0.6f);
ImGui.PushID(1);ImGui.Combo("", ref c, "Translate\0Rotate\0Scale\0");
ImGui.PopID();ImGui.SameLine();if (ImGui.Button("Reset All")) t.Reset();
Layout("Position");ImGui.Text(t.Position.ToString());
Layout("Rotation");ImGui.Text(t.Rotation.ToString());
Layout("Scale");ImGui.Text(t.Scale.ToString());
_guizmoOperation = c switch
{
0 => OPERATION.TRANSLATE,
1 => OPERATION.ROTATE,
2 => OPERATION.SCALE,
_ => OPERATION.UNIVERSAL
};
ImGui.EndTable();
}
ImGui.EndTabItem();
}
@ -719,8 +735,8 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
{
(model.Materials[section.MaterialIndex].GetSelectedTexture() ?? s.Renderer.Options.Icons["noimage"]).ImGuiTextureInspector();
}
ImGui.End(); // if window is collapsed
}
ImGui.End();
}
private void DrawSkeletonTree(Snooper s)
@ -741,8 +757,8 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
ImGui.EndTable();
}
}
ImGui.End(); // if window is collapsed
}
ImGui.End();
ImGui.PopStyleVar();
}
@ -756,41 +772,54 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
largest.Y -= ImGui.GetScrollY();
var size = new Vector2(largest.X, largest.Y);
var pos = ImGui.GetWindowPos();
var fHeight = ImGui.GetFrameHeight();
s.Renderer.CameraOp.AspectRatio = size.X / size.Y;
ImGui.Image(s.Framebuffer.GetPointer(), size, new Vector2(0, 1), new Vector2(1, 0), Vector4.One);
ImGui.Image(s.Framebuffer.GetPointer(), size, new Vector2(0, 1), new Vector2(1, 0));
if (ImGui.IsItemHovered())
if (_transformOpen)
{
// 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.Options.SelectModel(guid);
ImGui.SetWindowFocus("Outliner");
ImGui.SetWindowFocus("Details");
}
ImGuizmo.SetDrawlist(ImGui.GetWindowDrawList());
ImGuizmo.SetRect(pos.X, pos.Y + fHeight, size.X, size.Y);
DrawGuizmo(s);
}
if (ImGui.IsMouseDragging(ImGuiMouseButton.Left) && _viewportFocus)
if (!ImGuizmo.IsUsing())
{
s.Renderer.CameraOp.Modify(ImGui.GetIO().MouseDelta);
}
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.Options.SelectModel(guid);
ImGui.SetWindowFocus("Outliner");
ImGui.SetWindowFocus("Details");
}
}
// if left button up and mouse was in viewport
if (ImGui.IsMouseReleased(ImGuiMouseButton.Left) && _viewportFocus)
{
_viewportFocus = false;
s.CursorState = CursorState.Normal;
if (_viewportFocus && ImGui.IsMouseDragging(ImGuiMouseButton.Left))
{
s.Renderer.CameraOp.Modify(ImGui.GetIO().MouseDelta);
}
// if left button up and mouse was in viewport
if (_viewportFocus && ImGui.IsMouseReleased(ImGuiMouseButton.Left))
{
_viewportFocus = false;
s.CursorState = CursorState.Normal;
}
}
const float margin = 7.5f;
var buttonWidth = 14.0f * ImGui.GetWindowDpiScale();
var basePos = new Vector2( size.X - buttonWidth - margin * 2, ImGui.GetFrameHeight() + margin);
var basePos = new Vector2( size.X - buttonWidth - margin * 2, fHeight + margin);
ImGui.SetCursorPos(basePos);
ImGui.PushStyleColor(ImGuiCol.Button, Vector4.Zero);
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, new Vector4(0.2f));
@ -821,6 +850,26 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
ImGui.PopStyleVar();
}
private void DrawGuizmo(Snooper s)
{
var enableGuizmo = s.Renderer.Options.TryGetModel(out var selected) && selected.IsVisible;
if (enableGuizmo)
{
var view = s.Renderer.CameraOp.GetViewMatrix();
var proj = s.Renderer.CameraOp.GetProjectionMatrix();
var transform = selected.Transforms[selected.SelectedInstance];
var matrix = transform.Matrix;
if (ImGuizmo.Manipulate(ref view.M11, ref proj.M11, _guizmoOperation, MODE.LOCAL, ref matrix.M11) &&
Matrix4x4.Invert(transform.Relation, out var invRelation)) // matrix * invRelation = local matrix
{
// ^ long story short: there was issues with other transformation methods
// that's one way of modifying root elements without breaking the world matrix
transform.ModifyLocal(matrix * invRelation);
}
}
}
public static void Popup(Action content)
{
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(4f));
@ -850,12 +899,13 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
{
if (ImGui.Begin(name, ImGuiWindowFlags.NoScrollbar))
{
Controller.PopFont();
Controller.Normal();
if (styled) PushStyleCompact();
content();
if (styled) PopStyleCompact();
ImGui.End();
ImGui.PopFont();
}
ImGui.End();
}
private void MeshWindow(string name, Renderer renderer, Action<Dictionary<string, Texture>, UModel> content, bool styled = true)
@ -908,7 +958,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
ImGui.GetCursorPosY() + (region.Y - size.Y) / 2));
Controller.Bold();
ImGui.TextColored(color, text);
Controller.PopFont();
ImGui.PopFont();
}
public static void Layout(string name, bool tooltip = false)
@ -1027,7 +1077,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
style.Colors[(int) ImGuiCol.TableRowBgAlt] = new Vector4(1.00f, 1.00f, 1.00f, 0.06f);
style.Colors[(int) ImGuiCol.TextSelectedBg] = new Vector4(0.26f, 0.59f, 0.98f, 0.35f);
style.Colors[(int) ImGuiCol.DragDropTarget] = new Vector4(1.00f, 1.00f, 0.00f, 0.90f);
style.Colors[(int) ImGuiCol.NavHighlight] = new Vector4(0.26f, 0.59f, 0.98f, 1.00f);
style.Colors[(int) ImGuiCol.NavCursor] = new Vector4(0.26f, 0.59f, 0.98f, 1.00f);
style.Colors[(int) ImGuiCol.NavWindowingHighlight] = new Vector4(1.00f, 1.00f, 1.00f, 0.70f);
style.Colors[(int) ImGuiCol.NavWindowingDimBg] = new Vector4(0.80f, 0.80f, 0.80f, 0.20f);
style.Colors[(int) ImGuiCol.ModalWindowDimBg] = new Vector4(0.80f, 0.80f, 0.80f, 0.35f);

View File

@ -1,6 +1,5 @@
using System.Numerics;
using CUE4Parse.UE4.Objects.Core.Math;
using ImGuiNET;
namespace FModel.Views.Snooper;
@ -16,63 +15,37 @@ public class Transform
public FQuat Rotation = FQuat.Identity;
public FVector Scale = FVector.OneVector;
public Matrix4x4 LocalMatrix =>
Matrix4x4.CreateScale(Scale.X, Scale.Z, Scale.Y) *
Matrix4x4.CreateFromQuaternion(Quaternion.Normalize(new Quaternion(Rotation.X, Rotation.Z, Rotation.Y, -Rotation.W))) *
Matrix4x4.CreateTranslation(Position.X, Position.Z, Position.Y);
private Matrix4x4? _saved;
public Matrix4x4 LocalMatrix => Matrix4x4.CreateScale(Scale.X, Scale.Z, Scale.Y) *
Matrix4x4.CreateFromQuaternion(Quaternion.Normalize(new Quaternion(Rotation.X, Rotation.Z, Rotation.Y, -Rotation.W))) *
Matrix4x4.CreateTranslation(Position.X, Position.Z, Position.Y);
public Matrix4x4 Matrix => LocalMatrix * Relation;
public void ImGuiTransform(float speed)
public void Save()
{
const float width = 100f;
_saved = LocalMatrix;
}
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
if (ImGui.TreeNode("Location"))
{
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("X", ref Position.X, speed, 0f, 0f, "%.2f m");
public void ModifyLocal(Matrix4x4 matrix)
{
Matrix4x4.Decompose(matrix, out var scale, out var rotation, out var position);
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("Y", ref Position.Z, speed, 0f, 0f, "%.2f m");
Scale.X = scale.X;
Scale.Y = scale.Z;
Scale.Z = scale.Y;
Rotation.X = rotation.X;
Rotation.Y = rotation.Z;
Rotation.Z = rotation.Y;
Rotation.W = -rotation.W;
Position.X = position.X;
Position.Z = position.Y;
Position.Y = position.Z;
}
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("Z", ref Position.Y, speed, 0f, 0f, "%.2f m");
ImGui.TreePop();
}
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
if (ImGui.TreeNode("Rotation"))
{
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("W", ref Rotation.W, .005f, 0f, 0f, "%.3f rad");
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("X", ref Rotation.X, .005f, 0f, 0f, "%.3f rad");
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("Y", ref Rotation.Z, .005f, 0f, 0f, "%.3f rad");
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("Z", ref Rotation.Y, .005f, 0f, 0f, "%.3f rad");
ImGui.TreePop();
}
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
if (ImGui.TreeNode("Scale"))
{
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("X", ref Scale.X, speed, 0f, 0f, "%.3f");
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("Y", ref Scale.Z, speed, 0f, 0f, "%.3f");
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("Z", ref Scale.Y, speed, 0f, 0f, "%.3f");
ImGui.TreePop();
}
public void Reset()
{
if (!_saved.HasValue) return;
ModifyLocal(_saved.Value);
}
public override string ToString() => Matrix.Translation.ToString();