mirror of
https://github.com/4sval/FModel.git
synced 2026-09-26 11:07:39 -05:00
poc snooper running
This commit is contained in:
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +1,6 @@
|
||||
[submodule "CUE4Parse"]
|
||||
path = CUE4Parse
|
||||
url = https://github.com/FabianFG/CUE4Parse
|
||||
[submodule "Snooper"]
|
||||
path = Snooper
|
||||
url = https://github.com/FModel/Snooper
|
||||
|
||||
Submodule CUE4Parse updated: c99a1d6dfc...746a6d779a
5
Directory.Build.props
Normal file
5
Directory.Build.props
Normal file
@@ -0,0 +1,5 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<CUE4ParseDir>$(MSBuildThisFileDirectory)CUE4Parse\</CUE4ParseDir>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -7,11 +7,10 @@ using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
using CUE4Parse;
|
||||
using FModel.Framework;
|
||||
using FModel.Services;
|
||||
using FModel.Settings;
|
||||
using FModel.Views.Snooper;
|
||||
using FModel.ViewModels;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog.Events;
|
||||
using Serilog.Sinks.SystemConsole.Themes;
|
||||
@@ -134,7 +133,7 @@ public partial class App
|
||||
.Filter.ByIncludingOnly(IsConversionLibrary)
|
||||
.WriteTo.Console(outputTemplate: template2, theme: AnsiConsoleTheme.Literate)
|
||||
.WriteTo.File(outputTemplate: template2, path: filePath, shared: true))
|
||||
.MinimumLevel.Override("CUE4Parse_Conversion", LogEventLevel.Verbose).WriteTo.Sink(ImGuiSink.Instance)
|
||||
// .MinimumLevel.Override("CUE4Parse_Conversion", LogEventLevel.Verbose).WriteTo.Sink(ImGuiSink.Instance)
|
||||
.CreateLogger();
|
||||
|
||||
CacheManager.MigrateLegacyFiles();
|
||||
@@ -150,6 +149,7 @@ public partial class App
|
||||
|
||||
private void AppExit(object sender, ExitEventArgs e)
|
||||
{
|
||||
SnooperViewModel.Instance.Dispose();
|
||||
Log.Information("––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––");
|
||||
Log.CloseAndFlush();
|
||||
UserSettings.Save();
|
||||
|
||||
@@ -169,19 +169,18 @@
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||
<PackageReference Include="NVorbis" Version="0.10.5" />
|
||||
<PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.1" />
|
||||
<PackageReference Include="OpenTK" Version="4.9.4" />
|
||||
<PackageReference Include="RestSharp" Version="114.0.0" />
|
||||
<PackageReference Include="Serilog" Version="4.3.1" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<PackageReference Include="SkiaSharp.HarfBuzz" Version="[2.88.9]" />
|
||||
<PackageReference Include="Svg.Skia" Version="[4.8.0]" /> <!--because of skia 3.0 in newer versions-->
|
||||
<PackageReference Include="Twizzle.ImGui-Bundle.NET" Version="1.91.5.2" />
|
||||
<PackageReference Include="VirtualizingWrapPanel" Version="2.5.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CUE4Parse\CUE4Parse-Conversion\CUE4Parse-Conversion.csproj" />
|
||||
<ProjectReference Include="..\CUE4Parse\CUE4Parse\CUE4Parse.csproj" />
|
||||
<ProjectReference Include="..\Snooper\Editor\Editor.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<Solution>
|
||||
<Project Path="../CUE4Parse/CUE4Parse-Conversion/CUE4Parse-Conversion.csproj" />
|
||||
<Project Path="../CUE4Parse/CUE4Parse/CUE4Parse.csproj" />
|
||||
<Project Path="../Snooper/Editor/Editor.csproj" />
|
||||
<Project Path="../Snooper/Snooper/Snooper.csproj" />
|
||||
<Project Path="FModel.csproj" />
|
||||
</Solution>
|
||||
|
||||
@@ -1,672 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
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;
|
||||
using ErrorCode = OpenTK.Graphics.OpenGL4.ErrorCode;
|
||||
using Keys = OpenTK.Windowing.GraphicsLibraryFramework.Keys;
|
||||
|
||||
namespace FModel.Framework;
|
||||
|
||||
public class ImGuiController : IDisposable
|
||||
{
|
||||
private bool _frameBegun;
|
||||
|
||||
private int _vertexArray;
|
||||
private int _vertexBuffer;
|
||||
private int _vertexBufferSize;
|
||||
private int _indexBuffer;
|
||||
private int _indexBufferSize;
|
||||
|
||||
//private Texture _fontTexture;
|
||||
|
||||
private int _fontTexture;
|
||||
|
||||
private int _shader;
|
||||
private int _shaderFontTextureLocation;
|
||||
private int _shaderProjectionMatrixLocation;
|
||||
|
||||
private int _windowWidth;
|
||||
private int _windowHeight;
|
||||
|
||||
public ImFontPtr FontNormal;
|
||||
public ImFontPtr FontBold;
|
||||
public ImFontPtr FontSemiBold;
|
||||
|
||||
private readonly Vector2 _scaleFactor = Vector2.One;
|
||||
public readonly float DpiScale = GetDpiScale();
|
||||
|
||||
private static bool KHRDebugAvailable = false;
|
||||
|
||||
public ImGuiController(int width, int height)
|
||||
{
|
||||
_windowWidth = width;
|
||||
_windowHeight = height;
|
||||
|
||||
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);
|
||||
ImGuizmo.SetImGuiContext(context);
|
||||
|
||||
var io = ImGui.GetIO();
|
||||
unsafe
|
||||
{
|
||||
var iniFileNamePtr = Marshal.StringToCoTaskMemUTF8(Path.Combine(UserSettings.Default.OutputDirectory, ".data", "imgui.ini"));
|
||||
io.NativePtr->IniFilename = (byte*)iniFileNamePtr;
|
||||
}
|
||||
|
||||
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
|
||||
var assemblyName = assembly.GetName().Name;
|
||||
byte[] LoadFont(string name)
|
||||
{
|
||||
using var stream = assembly.GetManifestResourceStream($"{assemblyName}.Resources.{name}")
|
||||
?? throw new FileNotFoundException($"Embedded font '{name}' not found.");
|
||||
using var ms = new MemoryStream();
|
||||
stream.CopyTo(ms);
|
||||
return ms.ToArray();
|
||||
}
|
||||
|
||||
var faSolid = LoadFont("fa-solid-900.otf");
|
||||
var faRegular = LoadFont("fa-regular-400.otf");
|
||||
var faBrands = LoadFont("fa-brands-400.otf");
|
||||
|
||||
unsafe
|
||||
{
|
||||
// FA5 icons live in E000–F8FF (brands/regular/solid)
|
||||
var iconRanges = stackalloc ushort[] { 0xe000, 0xf8ff, 0 };
|
||||
|
||||
var cfg = ImGuiNative.ImFontConfig_ImFontConfig();
|
||||
cfg->MergeMode = 1;
|
||||
cfg->PixelSnapH = 1;
|
||||
cfg->GlyphMinAdvanceX = 16f;
|
||||
// FontDataOwnedByAtlas = 0 because we manage the GCHandle lifetime ourselves
|
||||
cfg->FontDataOwnedByAtlas = 0;
|
||||
|
||||
void MergeFontAwesome(byte[] solid, byte[] regular, byte[] brands)
|
||||
{
|
||||
fixed (byte* pSolid = solid)
|
||||
fixed (byte* pRegular = regular)
|
||||
fixed (byte* pBrands = brands)
|
||||
{
|
||||
io.Fonts.AddFontFromMemoryTTF((IntPtr)pSolid, solid.Length, 14, (IntPtr)cfg, (IntPtr)iconRanges);
|
||||
io.Fonts.AddFontFromMemoryTTF((IntPtr)pRegular, regular.Length, 14, (IntPtr)cfg, (IntPtr)iconRanges);
|
||||
io.Fonts.AddFontFromMemoryTTF((IntPtr)pBrands, brands.Length, 14, (IntPtr)cfg, (IntPtr)iconRanges);
|
||||
}
|
||||
}
|
||||
|
||||
// If not found, Fallback to default ImGui Font
|
||||
var normalPath = @"C:\Windows\Fonts\segoeui.ttf";
|
||||
var boldPath = @"C:\Windows\Fonts\segoeuib.ttf";
|
||||
var semiBoldPath = @"C:\Windows\Fonts\seguisb.ttf";
|
||||
|
||||
if (File.Exists(normalPath))
|
||||
{
|
||||
FontNormal = io.Fonts.AddFontFromFileTTF(normalPath, 16 * DpiScale);
|
||||
MergeFontAwesome(faSolid, faRegular, faBrands);
|
||||
}
|
||||
if (File.Exists(boldPath))
|
||||
{
|
||||
FontBold = io.Fonts.AddFontFromFileTTF(boldPath, 16 * DpiScale);
|
||||
MergeFontAwesome(faSolid, faRegular, faBrands);
|
||||
}
|
||||
if (File.Exists(semiBoldPath))
|
||||
{
|
||||
FontSemiBold = io.Fonts.AddFontFromFileTTF(semiBoldPath, 16 * DpiScale);
|
||||
MergeFontAwesome(faSolid, faRegular, faBrands);
|
||||
}
|
||||
|
||||
ImGuiNative.ImFontConfig_destroy(cfg);
|
||||
}
|
||||
|
||||
io.Fonts.AddFontDefault();
|
||||
io.Fonts.Build(); // Build font atlas
|
||||
|
||||
io.BackendFlags |= ImGuiBackendFlags.RendererHasVtxOffset;
|
||||
io.ConfigFlags |= ImGuiConfigFlags.NavEnableKeyboard;
|
||||
io.ConfigFlags |= ImGuiConfigFlags.DockingEnable;
|
||||
io.Fonts.Flags |= ImFontAtlasFlags.NoBakedLines;
|
||||
io.ConfigDockingWithShift = true;
|
||||
io.ConfigWindowsMoveFromTitleBarOnly = true;
|
||||
io.BackendRendererUserData = 0;
|
||||
|
||||
CreateDeviceResources();
|
||||
}
|
||||
|
||||
public void Normal() => PushFont(FontNormal);
|
||||
public void Bold() => PushFont(FontBold);
|
||||
public void SemiBold() => PushFont(FontSemiBold);
|
||||
|
||||
private void PushFont(ImFontPtr ptr) => ImGui.PushFont(ptr);
|
||||
|
||||
public void WindowResized(int width, int height)
|
||||
{
|
||||
_windowWidth = width;
|
||||
_windowHeight = height;
|
||||
}
|
||||
|
||||
public void DestroyDeviceObjects()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public void CreateDeviceResources()
|
||||
{
|
||||
_vertexBufferSize = 10000;
|
||||
_indexBufferSize = 2000;
|
||||
|
||||
int prevVAO = GL.GetInteger(GetPName.VertexArrayBinding);
|
||||
int prevArrayBuffer = GL.GetInteger(GetPName.ArrayBufferBinding);
|
||||
|
||||
_vertexArray = GL.GenVertexArray();
|
||||
GL.BindVertexArray(_vertexArray);
|
||||
LabelObject(ObjectLabelIdentifier.VertexArray, _vertexArray, "ImGui");
|
||||
|
||||
_vertexBuffer = GL.GenBuffer();
|
||||
GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBuffer);
|
||||
LabelObject(ObjectLabelIdentifier.Buffer, _vertexBuffer, "VBO: ImGui");
|
||||
GL.BufferData(BufferTarget.ArrayBuffer, _vertexBufferSize, IntPtr.Zero, BufferUsageHint.DynamicDraw);
|
||||
|
||||
_indexBuffer = GL.GenBuffer();
|
||||
GL.BindBuffer(BufferTarget.ElementArrayBuffer, _indexBuffer);
|
||||
LabelObject(ObjectLabelIdentifier.Buffer, _indexBuffer, "EBO: ImGui");
|
||||
GL.BufferData(BufferTarget.ElementArrayBuffer, _indexBufferSize, IntPtr.Zero, BufferUsageHint.DynamicDraw);
|
||||
|
||||
RecreateFontDeviceTexture();
|
||||
|
||||
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;
|
||||
}";
|
||||
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);
|
||||
}";
|
||||
|
||||
_shader = CreateProgram("ImGui", VertexSource, FragmentSource);
|
||||
_shaderProjectionMatrixLocation = GL.GetUniformLocation(_shader, "projection_matrix");
|
||||
_shaderFontTextureLocation = GL.GetUniformLocation(_shader, "in_fontTexture");
|
||||
|
||||
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);
|
||||
|
||||
GL.EnableVertexAttribArray(0);
|
||||
GL.EnableVertexAttribArray(1);
|
||||
GL.EnableVertexAttribArray(2);
|
||||
|
||||
GL.BindVertexArray(prevVAO);
|
||||
GL.BindBuffer(BufferTarget.ArrayBuffer, prevArrayBuffer);
|
||||
|
||||
CheckGLError("End of ImGui setup");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recreates the device texture used to render text.
|
||||
/// </summary>
|
||||
public void RecreateFontDeviceTexture()
|
||||
{
|
||||
ImGuiIOPtr io = ImGui.GetIO();
|
||||
io.Fonts.GetTexDataAsRGBA32(out IntPtr pixels, out int width, out int height, out int bytesPerPixel);
|
||||
|
||||
int mips = (int)Math.Floor(Math.Log(Math.Max(width, height), 2));
|
||||
|
||||
int prevActiveTexture = GL.GetInteger(GetPName.ActiveTexture);
|
||||
GL.ActiveTexture(TextureUnit.Texture0);
|
||||
int prevTexture2D = GL.GetInteger(GetPName.TextureBinding2D);
|
||||
|
||||
_fontTexture = GL.GenTexture();
|
||||
GL.BindTexture(TextureTarget.Texture2D, _fontTexture);
|
||||
GL.TexStorage2D(TextureTarget2d.Texture2D, mips, SizedInternalFormat.Rgba8, width, height);
|
||||
LabelObject(ObjectLabelIdentifier.Texture, _fontTexture, "ImGui Text Atlas");
|
||||
|
||||
GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, width, height, PixelFormat.Bgra, PixelType.UnsignedByte, pixels);
|
||||
|
||||
GL.GenerateMipmap(GenerateMipmapTarget.Texture2D);
|
||||
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat);
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat);
|
||||
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMaxLevel, mips - 1);
|
||||
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
|
||||
|
||||
// Restore state
|
||||
GL.BindTexture(TextureTarget.Texture2D, prevTexture2D);
|
||||
GL.ActiveTexture((TextureUnit)prevActiveTexture);
|
||||
|
||||
io.Fonts.SetTexID((IntPtr)_fontTexture);
|
||||
|
||||
io.Fonts.ClearTexData();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders the ImGui draw list data.
|
||||
/// </summary>
|
||||
public void Render()
|
||||
{
|
||||
if (_frameBegun)
|
||||
{
|
||||
_frameBegun = false;
|
||||
ImGui.Render();
|
||||
RenderImDrawData(ImGui.GetDrawData());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates ImGui input and IO configuration state.
|
||||
/// </summary>
|
||||
public void Update(GameWindow wnd, float deltaSeconds)
|
||||
{
|
||||
if (_frameBegun)
|
||||
{
|
||||
ImGui.Render();
|
||||
}
|
||||
|
||||
SetPerFrameImGuiData(deltaSeconds);
|
||||
UpdateImGuiInput(wnd);
|
||||
|
||||
_frameBegun = true;
|
||||
ImGui.NewFrame();
|
||||
ImGuizmo.BeginFrame();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets per-frame data based on the associated window.
|
||||
/// This is called by Update(float).
|
||||
/// </summary>
|
||||
private void SetPerFrameImGuiData(float deltaSeconds)
|
||||
{
|
||||
ImGuiIOPtr io = ImGui.GetIO();
|
||||
io.DisplaySize = new Vector2(
|
||||
_windowWidth / _scaleFactor.X,
|
||||
_windowHeight / _scaleFactor.Y);
|
||||
io.DisplayFramebufferScale = _scaleFactor;
|
||||
io.DeltaTime = deltaSeconds; // DeltaTime is in seconds.
|
||||
}
|
||||
|
||||
readonly List<char> PressedChars = new List<char>();
|
||||
|
||||
private void UpdateImGuiInput(GameWindow wnd)
|
||||
{
|
||||
ImGuiIOPtr io = ImGui.GetIO();
|
||||
|
||||
var mState = wnd.MouseState;
|
||||
var kState = wnd.KeyboardState;
|
||||
|
||||
io.AddMousePosEvent(mState.X, mState.Y);
|
||||
io.AddMouseButtonEvent(0, mState[MouseButton.Left]);
|
||||
io.AddMouseButtonEvent(1, mState[MouseButton.Right]);
|
||||
io.AddMouseButtonEvent(2, mState[MouseButton.Middle]);
|
||||
io.AddMouseButtonEvent(3, mState[MouseButton.Button1]);
|
||||
io.AddMouseButtonEvent(4, mState[MouseButton.Button2]);
|
||||
io.AddMouseWheelEvent(mState.ScrollDelta.X, mState.ScrollDelta.Y);
|
||||
|
||||
foreach (Keys key in Enum.GetValues<Keys>())
|
||||
{
|
||||
if (key == Keys.Unknown) continue;
|
||||
io.AddKeyEvent(TranslateKey(key), kState.IsKeyDown(key));
|
||||
}
|
||||
|
||||
foreach (var c in PressedChars)
|
||||
{
|
||||
io.AddInputCharacter(c);
|
||||
}
|
||||
PressedChars.Clear();
|
||||
|
||||
io.KeyShift = kState.IsKeyDown(Keys.LeftShift) || kState.IsKeyDown(Keys.RightShift);
|
||||
io.KeyCtrl = kState.IsKeyDown(Keys.LeftControl) || kState.IsKeyDown(Keys.RightControl);
|
||||
io.KeyAlt = kState.IsKeyDown(Keys.LeftAlt) || kState.IsKeyDown(Keys.RightAlt);
|
||||
io.KeySuper = kState.IsKeyDown(Keys.LeftSuper) || kState.IsKeyDown(Keys.RightSuper);
|
||||
}
|
||||
|
||||
internal void PressChar(char keyChar)
|
||||
{
|
||||
PressedChars.Add(keyChar);
|
||||
}
|
||||
|
||||
private void RenderImDrawData(ImDrawDataPtr draw_data)
|
||||
{
|
||||
if (draw_data.CmdListsCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get intial state.
|
||||
int prevVAO = GL.GetInteger(GetPName.VertexArrayBinding);
|
||||
int prevArrayBuffer = GL.GetInteger(GetPName.ArrayBufferBinding);
|
||||
int prevProgram = GL.GetInteger(GetPName.CurrentProgram);
|
||||
bool prevBlendEnabled = GL.GetBoolean(GetPName.Blend);
|
||||
bool prevScissorTestEnabled = GL.GetBoolean(GetPName.ScissorTest);
|
||||
int prevBlendEquationRgb = GL.GetInteger(GetPName.BlendEquationRgb);
|
||||
int prevBlendEquationAlpha = GL.GetInteger(GetPName.BlendEquationAlpha);
|
||||
int prevBlendFuncSrcRgb = GL.GetInteger(GetPName.BlendSrcRgb);
|
||||
int prevBlendFuncSrcAlpha = GL.GetInteger(GetPName.BlendSrcAlpha);
|
||||
int prevBlendFuncDstRgb = GL.GetInteger(GetPName.BlendDstRgb);
|
||||
int prevBlendFuncDstAlpha = GL.GetInteger(GetPName.BlendDstAlpha);
|
||||
bool prevCullFaceEnabled = GL.GetBoolean(GetPName.CullFace);
|
||||
bool prevDepthTestEnabled = GL.GetBoolean(GetPName.DepthTest);
|
||||
int prevActiveTexture = GL.GetInteger(GetPName.ActiveTexture);
|
||||
GL.ActiveTexture(TextureUnit.Texture0);
|
||||
int prevTexture2D = GL.GetInteger(GetPName.TextureBinding2D);
|
||||
Span<int> prevScissorBox = stackalloc int[4];
|
||||
unsafe
|
||||
{
|
||||
fixed (int* iptr = &prevScissorBox[0])
|
||||
{
|
||||
GL.GetInteger(GetPName.ScissorBox, iptr);
|
||||
}
|
||||
}
|
||||
|
||||
// Bind the element buffer (thru the VAO) so that we can resize it.
|
||||
GL.BindVertexArray(_vertexArray);
|
||||
// Bind the vertex buffer so that we can resize it.
|
||||
GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBuffer);
|
||||
for (int i = 0; i < draw_data.CmdListsCount; i++)
|
||||
{
|
||||
ImDrawListPtr cmd_list = draw_data.CmdLists[i];
|
||||
|
||||
int vertexSize = cmd_list.VtxBuffer.Size * Marshal.SizeOf<ImDrawVert>();
|
||||
if (vertexSize > _vertexBufferSize)
|
||||
{
|
||||
int newSize = (int)Math.Max(_vertexBufferSize * 1.5f, vertexSize);
|
||||
|
||||
GL.BufferData(BufferTarget.ArrayBuffer, newSize, IntPtr.Zero, BufferUsageHint.DynamicDraw);
|
||||
_vertexBufferSize = newSize;
|
||||
}
|
||||
|
||||
int indexSize = cmd_list.IdxBuffer.Size * sizeof(ushort);
|
||||
if (indexSize > _indexBufferSize)
|
||||
{
|
||||
int newSize = (int)Math.Max(_indexBufferSize * 1.5f, indexSize);
|
||||
GL.BufferData(BufferTarget.ElementArrayBuffer, newSize, IntPtr.Zero, BufferUsageHint.DynamicDraw);
|
||||
_indexBufferSize = newSize;
|
||||
}
|
||||
}
|
||||
|
||||
// Setup orthographic projection matrix into our constant buffer
|
||||
ImGuiIOPtr io = ImGui.GetIO();
|
||||
var mvp = OpenTK.Mathematics.Matrix4.CreateOrthographicOffCenter(
|
||||
0.0f,
|
||||
io.DisplaySize.X,
|
||||
io.DisplaySize.Y,
|
||||
0.0f,
|
||||
-1.0f,
|
||||
1.0f);
|
||||
|
||||
GL.UseProgram(_shader);
|
||||
GL.UniformMatrix4(_shaderProjectionMatrixLocation, false, ref mvp);
|
||||
GL.Uniform1(_shaderFontTextureLocation, 0);
|
||||
CheckGLError("Projection");
|
||||
|
||||
GL.BindVertexArray(_vertexArray);
|
||||
CheckGLError("VAO");
|
||||
|
||||
draw_data.ScaleClipRects(io.DisplayFramebufferScale);
|
||||
|
||||
GL.Enable(EnableCap.Blend);
|
||||
GL.Enable(EnableCap.ScissorTest);
|
||||
GL.BlendEquation(BlendEquationMode.FuncAdd);
|
||||
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
|
||||
GL.Disable(EnableCap.CullFace);
|
||||
GL.Disable(EnableCap.DepthTest);
|
||||
|
||||
// Render command lists
|
||||
for (int n = 0; n < draw_data.CmdListsCount; n++)
|
||||
{
|
||||
ImDrawListPtr cmd_list = draw_data.CmdLists[n];
|
||||
|
||||
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);
|
||||
CheckGLError($"Data Idx {n}");
|
||||
|
||||
for (int cmd_i = 0; cmd_i < cmd_list.CmdBuffer.Size; cmd_i++)
|
||||
{
|
||||
ImDrawCmdPtr pcmd = cmd_list.CmdBuffer[cmd_i];
|
||||
if (pcmd.UserCallback != IntPtr.Zero)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
else
|
||||
{
|
||||
GL.ActiveTexture(TextureUnit.Texture0);
|
||||
GL.BindTexture(TextureTarget.Texture2D, (int)pcmd.TextureId);
|
||||
CheckGLError("Texture");
|
||||
|
||||
// We do _windowHeight - (int)clip.W instead of (int)clip.Y because gl has flipped Y when it comes to these coordinates
|
||||
var clip = pcmd.ClipRect;
|
||||
GL.Scissor((int)clip.X, _windowHeight - (int)clip.W, (int)(clip.Z - clip.X), (int)(clip.W - clip.Y));
|
||||
CheckGLError("Scissor");
|
||||
|
||||
if ((io.BackendFlags & ImGuiBackendFlags.RendererHasVtxOffset) != 0)
|
||||
{
|
||||
GL.DrawElementsBaseVertex(PrimitiveType.Triangles, (int)pcmd.ElemCount, DrawElementsType.UnsignedShort, (IntPtr)(pcmd.IdxOffset * sizeof(ushort)), unchecked((int)pcmd.VtxOffset));
|
||||
}
|
||||
else
|
||||
{
|
||||
GL.DrawElements(BeginMode.Triangles, (int)pcmd.ElemCount, DrawElementsType.UnsignedShort, (int)pcmd.IdxOffset * sizeof(ushort));
|
||||
}
|
||||
CheckGLError("Draw");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GL.Disable(EnableCap.Blend);
|
||||
GL.Disable(EnableCap.ScissorTest);
|
||||
|
||||
// Reset state
|
||||
GL.BindTexture(TextureTarget.Texture2D, prevTexture2D);
|
||||
GL.ActiveTexture((TextureUnit)prevActiveTexture);
|
||||
GL.UseProgram(prevProgram);
|
||||
GL.BindVertexArray(prevVAO);
|
||||
GL.Scissor(prevScissorBox[0], prevScissorBox[1], prevScissorBox[2], prevScissorBox[3]);
|
||||
GL.BindBuffer(BufferTarget.ArrayBuffer, prevArrayBuffer);
|
||||
GL.BlendEquationSeparate((BlendEquationMode)prevBlendEquationRgb, (BlendEquationMode)prevBlendEquationAlpha);
|
||||
GL.BlendFuncSeparate(
|
||||
(BlendingFactorSrc)prevBlendFuncSrcRgb,
|
||||
(BlendingFactorDest)prevBlendFuncDstRgb,
|
||||
(BlendingFactorSrc)prevBlendFuncSrcAlpha,
|
||||
(BlendingFactorDest)prevBlendFuncDstAlpha);
|
||||
if (prevBlendEnabled) GL.Enable(EnableCap.Blend); else GL.Disable(EnableCap.Blend);
|
||||
if (prevDepthTestEnabled) GL.Enable(EnableCap.DepthTest); else GL.Disable(EnableCap.DepthTest);
|
||||
if (prevCullFaceEnabled) GL.Enable(EnableCap.CullFace); else GL.Disable(EnableCap.CullFace);
|
||||
if (prevScissorTestEnabled) GL.Enable(EnableCap.ScissorTest); else GL.Disable(EnableCap.ScissorTest);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Frees all graphics resources used by the renderer.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
GL.DeleteVertexArray(_vertexArray);
|
||||
GL.DeleteBuffer(_vertexBuffer);
|
||||
GL.DeleteBuffer(_indexBuffer);
|
||||
|
||||
GL.DeleteTexture(_fontTexture);
|
||||
GL.DeleteProgram(_shader);
|
||||
}
|
||||
|
||||
public static void LabelObject(ObjectLabelIdentifier objLabelIdent, int glObject, string name)
|
||||
{
|
||||
if (KHRDebugAvailable)
|
||||
GL.ObjectLabel(objLabelIdent, glObject, name.Length, name);
|
||||
}
|
||||
|
||||
static bool IsExtensionSupported(string name)
|
||||
{
|
||||
int n = GL.GetInteger(GetPName.NumExtensions);
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
string extension = GL.GetString(StringNameIndexed.Extensions, i);
|
||||
if (extension == name) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static int CreateProgram(string name, string vertexSource, string fragmentSoruce)
|
||||
{
|
||||
int program = GL.CreateProgram();
|
||||
LabelObject(ObjectLabelIdentifier.Program, program, $"Program: {name}");
|
||||
|
||||
int vertex = CompileShader(name, ShaderType.VertexShader, vertexSource);
|
||||
int fragment = CompileShader(name, ShaderType.FragmentShader, fragmentSoruce);
|
||||
|
||||
GL.AttachShader(program, vertex);
|
||||
GL.AttachShader(program, fragment);
|
||||
|
||||
GL.LinkProgram(program);
|
||||
|
||||
GL.GetProgram(program, GetProgramParameterName.LinkStatus, out int success);
|
||||
if (success == 0)
|
||||
{
|
||||
string info = GL.GetProgramInfoLog(program);
|
||||
Debug.WriteLine($"GL.LinkProgram had info log [{name}]:\n{info}");
|
||||
}
|
||||
|
||||
GL.DetachShader(program, vertex);
|
||||
GL.DetachShader(program, fragment);
|
||||
|
||||
GL.DeleteShader(vertex);
|
||||
GL.DeleteShader(fragment);
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
private static int CompileShader(string name, ShaderType type, string source)
|
||||
{
|
||||
int shader = GL.CreateShader(type);
|
||||
LabelObject(ObjectLabelIdentifier.Shader, shader, $"Shader: {name}");
|
||||
|
||||
GL.ShaderSource(shader, source);
|
||||
GL.CompileShader(shader);
|
||||
|
||||
GL.GetShader(shader, ShaderParameter.CompileStatus, out int success);
|
||||
if (success == 0)
|
||||
{
|
||||
string info = GL.GetShaderInfoLog(shader);
|
||||
Debug.WriteLine($"GL.CompileShader for shader '{name}' [{type}] had info log:\n{info}");
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
public static void CheckGLError(string title)
|
||||
{
|
||||
ErrorCode error;
|
||||
int i = 1;
|
||||
while ((error = GL.GetError()) != ErrorCode.NoError)
|
||||
{
|
||||
Debug.Print($"{title} ({i++}): {error}");
|
||||
}
|
||||
}
|
||||
|
||||
public static float GetDpiScale()
|
||||
{
|
||||
return Math.Max((float)(Screen.PrimaryScreen.Bounds.Width / SystemParameters.PrimaryScreenWidth), (float)(Screen.PrimaryScreen.Bounds.Height / SystemParameters.PrimaryScreenHeight));
|
||||
}
|
||||
|
||||
public static ImGuiKey TranslateKey(Keys key)
|
||||
{
|
||||
if (key >= Keys.D0 && key <= Keys.D9)
|
||||
return key - Keys.D0 + ImGuiKey._0;
|
||||
|
||||
if (key >= Keys.A && key <= Keys.Z)
|
||||
return key - Keys.A + ImGuiKey.A;
|
||||
|
||||
if (key >= Keys.KeyPad0 && key <= Keys.KeyPad9)
|
||||
return key - Keys.KeyPad0 + ImGuiKey.Keypad0;
|
||||
|
||||
if (key >= Keys.F1 && key <= Keys.F24)
|
||||
return key - Keys.F1 + ImGuiKey.F24;
|
||||
|
||||
return key switch
|
||||
{
|
||||
Keys.Tab => ImGuiKey.Tab,
|
||||
Keys.Left => ImGuiKey.LeftArrow,
|
||||
Keys.Right => ImGuiKey.RightArrow,
|
||||
Keys.Up => ImGuiKey.UpArrow,
|
||||
Keys.Down => ImGuiKey.DownArrow,
|
||||
Keys.PageUp => ImGuiKey.PageUp,
|
||||
Keys.PageDown => ImGuiKey.PageDown,
|
||||
Keys.Home => ImGuiKey.Home,
|
||||
Keys.End => ImGuiKey.End,
|
||||
Keys.Insert => ImGuiKey.Insert,
|
||||
Keys.Delete => ImGuiKey.Delete,
|
||||
Keys.Backspace => ImGuiKey.Backspace,
|
||||
Keys.Space => ImGuiKey.Space,
|
||||
Keys.Enter => ImGuiKey.Enter,
|
||||
Keys.Escape => ImGuiKey.Escape,
|
||||
Keys.Apostrophe => ImGuiKey.Apostrophe,
|
||||
Keys.Comma => ImGuiKey.Comma,
|
||||
Keys.Minus => ImGuiKey.Minus,
|
||||
Keys.Period => ImGuiKey.Period,
|
||||
Keys.Slash => ImGuiKey.Slash,
|
||||
Keys.Semicolon => ImGuiKey.Semicolon,
|
||||
Keys.Equal => ImGuiKey.Equal,
|
||||
Keys.LeftBracket => ImGuiKey.LeftBracket,
|
||||
Keys.Backslash => ImGuiKey.Backslash,
|
||||
Keys.RightBracket => ImGuiKey.RightBracket,
|
||||
Keys.GraveAccent => ImGuiKey.GraveAccent,
|
||||
Keys.CapsLock => ImGuiKey.CapsLock,
|
||||
Keys.ScrollLock => ImGuiKey.ScrollLock,
|
||||
Keys.NumLock => ImGuiKey.NumLock,
|
||||
Keys.PrintScreen => ImGuiKey.PrintScreen,
|
||||
Keys.Pause => ImGuiKey.Pause,
|
||||
Keys.KeyPadDecimal => ImGuiKey.KeypadDecimal,
|
||||
Keys.KeyPadDivide => ImGuiKey.KeypadDivide,
|
||||
Keys.KeyPadMultiply => ImGuiKey.KeypadMultiply,
|
||||
Keys.KeyPadSubtract => ImGuiKey.KeypadSubtract,
|
||||
Keys.KeyPadAdd => ImGuiKey.KeypadAdd,
|
||||
Keys.KeyPadEnter => ImGuiKey.KeypadEnter,
|
||||
Keys.KeyPadEqual => ImGuiKey.KeypadEqual,
|
||||
Keys.LeftShift => ImGuiKey.ModShift,
|
||||
Keys.LeftControl => ImGuiKey.LeftCtrl,
|
||||
Keys.LeftAlt => ImGuiKey.LeftAlt,
|
||||
Keys.LeftSuper => ImGuiKey.LeftSuper,
|
||||
Keys.RightShift => ImGuiKey.RightShift,
|
||||
Keys.RightControl => ImGuiKey.RightCtrl,
|
||||
Keys.RightAlt => ImGuiKey.RightAlt,
|
||||
Keys.RightSuper => ImGuiKey.RightSuper,
|
||||
Keys.Menu => ImGuiKey.Menu,
|
||||
_ => ImGuiKey.None
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -123,7 +123,7 @@ public partial class MainWindow
|
||||
_applicationView.CUE4Parse.InitMappings(),
|
||||
ApplicationViewModel.InitDetex(),
|
||||
ApplicationViewModel.InitVgmStream(),
|
||||
ApplicationViewModel.InitImGuiSettings(newOrUpdated),
|
||||
// ApplicationViewModel.InitImGuiSettings(newOrUpdated),
|
||||
Task.Run(() =>
|
||||
{
|
||||
if (UserSettings.Default.DiscordRpc == EDiscordRpc.Always)
|
||||
|
||||
@@ -12,7 +12,6 @@ using FModel.Extensions.Themes;
|
||||
using FModel.Framework;
|
||||
using FModel.ViewModels;
|
||||
using FModel.ViewModels.ApiEndpoints.Models;
|
||||
using FModel.Views.Snooper;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace FModel.Settings;
|
||||
@@ -564,13 +563,6 @@ public sealed class UserSettings : ViewModel
|
||||
set => SetProperty(ref _animateWithRotationOnly, value);
|
||||
}
|
||||
|
||||
private Camera.WorldMode _cameraMode = Camera.WorldMode.Arcball;
|
||||
public Camera.WorldMode CameraMode
|
||||
{
|
||||
get => _cameraMode;
|
||||
set => SetProperty(ref _cameraMode, value);
|
||||
}
|
||||
|
||||
private int _previewMaxTextureSize = 1024;
|
||||
public int PreviewMaxTextureSize
|
||||
{
|
||||
|
||||
@@ -9,7 +9,6 @@ using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using AdonisUI.Controls;
|
||||
using CUE4Parse;
|
||||
using CUE4Parse.Compression;
|
||||
@@ -78,11 +77,8 @@ using FModel.Services;
|
||||
using FModel.Settings;
|
||||
using FModel.Views;
|
||||
using FModel.Views.Resources.Controls;
|
||||
using FModel.Views.Snooper;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using OpenTK.Windowing.Common;
|
||||
using OpenTK.Windowing.Desktop;
|
||||
using Serilog;
|
||||
using SkiaSharp;
|
||||
using Svg.Skia;
|
||||
@@ -116,39 +112,6 @@ public class CUE4ParseViewModel : ViewModel
|
||||
set => SetProperty(ref _modelIsWaitingAnimation, value);
|
||||
}
|
||||
|
||||
public bool IsSnooperOpen => _snooper is { Exists: true, IsVisible: true };
|
||||
private Snooper _snooper;
|
||||
public Snooper SnooperViewer
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_snooper != null) return _snooper;
|
||||
|
||||
return Application.Current.Dispatcher.Invoke(delegate
|
||||
{
|
||||
var scale = ImGuiController.GetDpiScale();
|
||||
var htz = Snooper.GetMaxRefreshFrequency();
|
||||
return _snooper = new Snooper(
|
||||
new GameWindowSettings { UpdateFrequency = htz },
|
||||
new NativeWindowSettings
|
||||
{
|
||||
ClientSize = new OpenTK.Mathematics.Vector2i(
|
||||
Convert.ToInt32(SystemParameters.MaximizedPrimaryScreenWidth * .75 * scale),
|
||||
Convert.ToInt32(SystemParameters.MaximizedPrimaryScreenHeight * .85 * scale)),
|
||||
NumberOfSamples = Constants.SAMPLES_COUNT,
|
||||
WindowBorder = WindowBorder.Resizable,
|
||||
Flags = ContextFlags.ForwardCompatible,
|
||||
Profile = ContextProfile.Core,
|
||||
Vsync = VSyncMode.Adaptive,
|
||||
APIVersion = new Version(4, 6),
|
||||
StartVisible = false,
|
||||
StartFocused = false,
|
||||
Title = "3D Viewer"
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public AbstractVfsFileProvider Provider { get; }
|
||||
public GameDirectoryViewModel GameDirectory { get; }
|
||||
public AssetsFolderViewModel AssetsFolder { get; }
|
||||
@@ -1569,21 +1532,22 @@ public class CUE4ParseViewModel : ViewModel
|
||||
pkg.Name.Contains("/RenderSwitch_Materials/", StringComparison.OrdinalIgnoreCase) ||
|
||||
pkg.Name.Contains("/MI_BPTile/", StringComparison.OrdinalIgnoreCase))):
|
||||
{
|
||||
if (SnooperViewer.TryLoadExport(cancellationToken, dummy, pointer.Object))
|
||||
SnooperViewer.Run();
|
||||
SnooperViewModel.Instance.Load(pointer.Object.Value);
|
||||
// if (SnooperViewer.TryLoadExport(cancellationToken, dummy, pointer.Object))
|
||||
// SnooperViewer.Run();
|
||||
return true;
|
||||
}
|
||||
case UMaterialInstance when isNone && ModelIsOverwritingMaterial && pointer.Object.Value is UMaterialInstance m:
|
||||
{
|
||||
SnooperViewer.Renderer.Swap(m);
|
||||
SnooperViewer.Run();
|
||||
// SnooperViewer.Renderer.Swap(m);
|
||||
// SnooperViewer.Run();
|
||||
return true;
|
||||
}
|
||||
case UAnimSequenceBase when isNone && UserSettings.Default.PreviewAnimations || ModelIsWaitingAnimation:
|
||||
{
|
||||
// animate all animations using their specified skeleton or when we explicitly asked for a loaded model to be animated (ignoring whether we wanted to preview animations)
|
||||
SnooperViewer.Renderer.Animate(pointer.Object.Value);
|
||||
SnooperViewer.Run();
|
||||
// SnooperViewer.Renderer.Animate(pointer.Object.Value);
|
||||
// SnooperViewer.Run();
|
||||
return true;
|
||||
}
|
||||
case UStaticMesh when HasFlag(bulk, EBulkType.Meshes):
|
||||
|
||||
@@ -38,7 +38,8 @@ public class MenuCommand : ViewModelCommand<ApplicationViewModel>
|
||||
contextViewModel.CUE4Parse.TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(contextViewModel.CUE4Parse.GameDirectory.DirectoryFiles, Formatting.Indented), false, false);
|
||||
break;
|
||||
case "Views_3dViewer":
|
||||
contextViewModel.CUE4Parse.SnooperViewer.Run();
|
||||
SnooperViewModel.Instance.Load(null);
|
||||
SnooperViewModel.Instance.Run();
|
||||
break;
|
||||
case "Views_ExportSession":
|
||||
Helper.OpenWindow<AdonisWindow>("Export Session", () => new ExportSessionWindow().Show());
|
||||
|
||||
@@ -18,7 +18,6 @@ using FModel.Framework;
|
||||
using FModel.Settings;
|
||||
using FModel.Views;
|
||||
using FModel.Views.Resources.Controls;
|
||||
using FModel.Views.Snooper;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace FModel.ViewModels;
|
||||
@@ -145,7 +144,7 @@ public class ExportSessionViewModel : ViewModel
|
||||
|
||||
private ExportSessionViewModel()
|
||||
{
|
||||
ImGuiSink.Instance.OnExporterLogEvent += OnLogEvent;
|
||||
// ImGuiSink.Instance.OnExporterLogEvent += OnLogEvent;
|
||||
}
|
||||
|
||||
private void OnLogEvent(LogEvent log)
|
||||
|
||||
197
FModel/ViewModels/SnooperViewModel.cs
Normal file
197
FModel/ViewModels/SnooperViewModel.cs
Normal file
@@ -0,0 +1,197 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
|
||||
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
|
||||
using CUE4Parse.UE4.Objects.Engine;
|
||||
using Editor;
|
||||
using FModel.Framework;
|
||||
using FModel.Services;
|
||||
using OpenTK.Windowing.Desktop;
|
||||
using Serilog;
|
||||
using Snooper.Rendering.Actors;
|
||||
using Snooper.Rendering.Components;
|
||||
using Snooper.Rendering.Components.Light;
|
||||
using Snooper.Rendering.Components.Primitive;
|
||||
using Snooper.Rendering.Components.Transforms;
|
||||
|
||||
namespace FModel.ViewModels;
|
||||
|
||||
public class SnooperViewModel : ViewModel, IDisposable
|
||||
{
|
||||
public static SnooperViewModel Instance { get; } = new();
|
||||
|
||||
private readonly Lazy<EditorWindow> _editor;
|
||||
|
||||
private SnooperViewModel()
|
||||
{
|
||||
var scale = GetDpiScale();
|
||||
var htz = GetMaxRefreshFrequency();
|
||||
var width = Convert.ToInt32(SystemParameters.MaximizedPrimaryScreenWidth * .75 * scale);
|
||||
var height = Convert.ToInt32(SystemParameters.MaximizedPrimaryScreenHeight * .85 * scale);
|
||||
|
||||
_editor = new Lazy<EditorWindow>(() => StartEditor(htz, width, height));
|
||||
}
|
||||
|
||||
private static EditorWindow StartEditor(int htz, int width, int height)
|
||||
{
|
||||
GLFWProvider.CheckForMainThread = false;
|
||||
|
||||
var ready = new ManualResetEventSlim();
|
||||
EditorWindow? editor = null;
|
||||
Exception? failure = null;
|
||||
|
||||
new Thread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
editor = new EditorWindow(htz, width, height, ApplicationService.ApplicationView.CUE4Parse.Provider, false, true);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
failure = e;
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ready.Set();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
editor.Run();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Snooper crashed");
|
||||
}
|
||||
}) { IsBackground = true, Name = "Snooper" }.Start();
|
||||
|
||||
ready.Wait();
|
||||
return editor ?? throw new InvalidOperationException("Snooper failed to start", failure);
|
||||
}
|
||||
|
||||
public void Load(UObject? obj)
|
||||
{
|
||||
var scene = new Actor("Example Scene");
|
||||
scene.Components.Add(new BoxComponent(Vector3.Zero, Vector3.One));
|
||||
|
||||
var camera = new CameraActor("Camera");
|
||||
camera.CameraComponent.LocalTransform.Position = new Vector3(1, 2, -0.5f);
|
||||
camera.CameraComponent.LocalTransform.Rotation = new Quaternion(0, -1, 0, 1);
|
||||
scene.Children.Add(camera);
|
||||
|
||||
var sun = new Actor("Sun Light");
|
||||
sun.Components.Add(new DirectionalLightComponent(MathF.PI, new Vector3(1.0f, 0.87f, 0.72f), new Transform(new Quaternion(new Vector3(0.5f, -0.5f, 0.0f), 1.0f)), "Directional Light"));
|
||||
scene.Children.Add(sun);
|
||||
|
||||
GridComponent gridComponent = new OpaqueGridComponent();
|
||||
switch (obj)
|
||||
{
|
||||
case UStaticMesh sm:
|
||||
scene.Children.Add(new MeshActor(sm));
|
||||
break;
|
||||
case USkeletalMesh sk:
|
||||
scene.Children.Add(new MeshActor(sk));
|
||||
break;
|
||||
case UWorld w:
|
||||
gridComponent = new GridComponent();
|
||||
scene.Children.Add(new WorldActor(w));
|
||||
break;
|
||||
}
|
||||
var grid = new Actor("Grid");
|
||||
grid.Components.Add(gridComponent);
|
||||
scene.Children.Insert(0, grid);
|
||||
|
||||
var editor = _editor.Value;
|
||||
editor.Invoke(() =>
|
||||
{
|
||||
editor.Manager.LoadScene(scene);
|
||||
editor.Show();
|
||||
});
|
||||
}
|
||||
|
||||
public void Run()
|
||||
{
|
||||
var editor = _editor.Value;
|
||||
editor.Invoke(editor.Show);
|
||||
}
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool EnumDisplaySettings(string deviceName, int modeNum, ref DEVMODE devMode);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct DEVMODE
|
||||
{
|
||||
private const int CCHDEVICENAME = 0x20;
|
||||
private const int CCHFORMNAME = 0x20;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)]
|
||||
public string dmDeviceName;
|
||||
public short dmSpecVersion;
|
||||
public short dmDriverVersion;
|
||||
public short dmSize;
|
||||
public short dmDriverExtra;
|
||||
public int dmFields;
|
||||
public int dmPositionX;
|
||||
public int dmPositionY;
|
||||
public ScreenOrientation dmDisplayOrientation;
|
||||
public int dmDisplayFixedOutput;
|
||||
public short dmColor;
|
||||
public short dmDuplex;
|
||||
public short dmYResolution;
|
||||
public short dmTTOption;
|
||||
public short dmCollate;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)]
|
||||
public string dmFormName;
|
||||
public short dmLogPixels;
|
||||
public int dmBitsPerPel;
|
||||
public int dmPelsWidth;
|
||||
public int dmPelsHeight;
|
||||
public int dmDisplayFlags;
|
||||
public int dmDisplayFrequency;
|
||||
public int dmICMMethod;
|
||||
public int dmICMIntent;
|
||||
public int dmMediaType;
|
||||
public int dmDitherType;
|
||||
public int dmReserved1;
|
||||
public int dmReserved2;
|
||||
public int dmPanningWidth;
|
||||
public int dmPanningHeight;
|
||||
|
||||
}
|
||||
|
||||
private static float GetDpiScale()
|
||||
{
|
||||
if (Screen.PrimaryScreen is not { } primaryScreen)
|
||||
return 1.0f;
|
||||
|
||||
return (float)Math.Max(
|
||||
primaryScreen.Bounds.Width / SystemParameters.PrimaryScreenWidth,
|
||||
primaryScreen.Bounds.Height / SystemParameters.PrimaryScreenHeight
|
||||
);
|
||||
}
|
||||
|
||||
private static int GetMaxRefreshFrequency()
|
||||
{
|
||||
var rf = 60;
|
||||
var vDevMode = new DEVMODE();
|
||||
var i = 0;
|
||||
while (EnumDisplaySettings(null, i, ref vDevMode))
|
||||
{
|
||||
i++;
|
||||
rf = Math.Max(rf, vDevMode.dmDisplayFrequency);
|
||||
}
|
||||
|
||||
return rf;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_editor.IsValueCreated) return;
|
||||
_editor.Value.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -49,9 +49,9 @@ public class ThreadWorkerViewModel : ViewModel
|
||||
|
||||
public async Task Begin(Action<CancellationToken> action)
|
||||
{
|
||||
if (_applicationView.CUE4Parse.IsSnooperOpen)
|
||||
/*if (_applicationView.CUE4Parse.IsSnooperOpen)
|
||||
_applicationView.CUE4Parse.SnooperViewer.Close();
|
||||
else if (!_applicationView.Status.IsReady)
|
||||
else */if (!_applicationView.Status.IsReady)
|
||||
{
|
||||
SignalOperationInProgress();
|
||||
return;
|
||||
@@ -88,8 +88,8 @@ public class ThreadWorkerViewModel : ViewModel
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
_applicationView.Status.SetStatus(EStatusKind.Stopped);
|
||||
if (_applicationView.CUE4Parse.IsSnooperOpen)
|
||||
_applicationView.CUE4Parse.SnooperViewer.Close();
|
||||
// if (_applicationView.CUE4Parse.IsSnooperOpen)
|
||||
// _applicationView.CUE4Parse.SnooperViewer.Close();
|
||||
CurrentCancellationTokenSource = null; // kill token
|
||||
OperationCancelled = true;
|
||||
OperationCancelled = false;
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using CUE4Parse_Conversion;
|
||||
using CUE4Parse_Conversion.Writers.ActorX.Structs.Animations;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using FModel.Settings;
|
||||
using FModel.Views.Snooper.Models;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace FModel.Views.Snooper.Animations;
|
||||
|
||||
public class Animation : IExportableThing, IDisposable
|
||||
{
|
||||
private readonly UObject _export;
|
||||
|
||||
public readonly CAnimSet UnrealAnim;
|
||||
public readonly string Path;
|
||||
public readonly string Name;
|
||||
public readonly Sequence[] Sequences;
|
||||
public readonly float StartTime; // Animation Start Time
|
||||
public readonly float EndTime; // Animation End Time
|
||||
public readonly float TotalElapsedTime; // Animation Max Time
|
||||
public readonly Dictionary<int, float> Framing;
|
||||
|
||||
public bool IsActive;
|
||||
public bool IsSelected;
|
||||
|
||||
public readonly List<FGuid> AttachedModels;
|
||||
|
||||
public Animation(UObject export)
|
||||
{
|
||||
_export = export;
|
||||
Path = _export.GetPathName();
|
||||
Name = _export.Name;
|
||||
Sequences = [];
|
||||
Framing = new Dictionary<int, float>();
|
||||
AttachedModels = [];
|
||||
}
|
||||
|
||||
public Animation(UObject export, CAnimSet animSet) : this(export)
|
||||
{
|
||||
UnrealAnim = animSet;
|
||||
|
||||
Sequences = new Sequence[UnrealAnim.Sequences.Count];
|
||||
for (int i = 0; i < Sequences.Length; i++)
|
||||
{
|
||||
Sequences[i] = new Sequence(UnrealAnim.Sequences[i]);
|
||||
EndTime = Sequences[i].EndTime;
|
||||
}
|
||||
|
||||
TotalElapsedTime = EndTime;
|
||||
if (Sequences.Length > 0)
|
||||
StartTime = Sequences[0].StartTime;
|
||||
}
|
||||
|
||||
public Animation(UObject export, CAnimSet animSet, params FGuid[] animatedModels) : this(export, animSet)
|
||||
{
|
||||
AttachedModels.AddRange(animatedModels);
|
||||
}
|
||||
|
||||
public void TimeCalculation(float elapsedTime)
|
||||
{
|
||||
for (int i = 0; i < Sequences.Length; i++)
|
||||
{
|
||||
var sequence = Sequences[i];
|
||||
if (elapsedTime <= sequence.EndTime && elapsedTime >= sequence.StartTime)
|
||||
{
|
||||
Framing[i] = (elapsedTime - sequence.StartTime) * sequence.RateScale / sequence.SecondsPerFrame;
|
||||
}
|
||||
else Framing.Remove(i);
|
||||
}
|
||||
|
||||
if (elapsedTime >= TotalElapsedTime)
|
||||
Framing.Clear();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
AttachedModels.Clear();
|
||||
}
|
||||
|
||||
public void ImGuiAnimation(Snooper s, ImDrawListPtr drawList, ImFontPtr fontPtr, Vector2 timelineP0, Vector2 treeP0, Vector2 timeStep, Vector2 timeRatio, float y, float t, int i)
|
||||
{
|
||||
var name = $"{Name}##{i}";
|
||||
var p1 = new Vector2(timelineP0.X + StartTime * timeRatio.X + t, y + t);
|
||||
var p2 = new Vector2(timelineP0.X + EndTime * timeRatio.X - t, y + timeStep.Y - t);
|
||||
|
||||
ImGui.SetCursorScreenPos(p1);
|
||||
ImGui.InvisibleButton($"timeline_sequencetracker_{name}", new Vector2(EndTime * timeRatio.X - t, timeStep.Y - t), ImGuiButtonFlags.MouseButtonLeft);
|
||||
IsActive = ImGui.IsItemActive();
|
||||
IsSelected = s.Renderer.Options.SelectedAnimation == i;
|
||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
|
||||
{
|
||||
s.Renderer.Options.SelectAnimation(i);
|
||||
}
|
||||
Popup(s, i);
|
||||
|
||||
drawList.AddRectFilled(p1, p2, IsSelected ? 0xFF48B048 : 0xFF175F17, 5.0f, ImDrawFlags.RoundCornersTop);
|
||||
for (int j = 0; j < Sequences.Length; j++)
|
||||
{
|
||||
Sequences[j].DrawSequence(drawList, fontPtr, timelineP0.X, p2, timeStep, timeRatio, t, IsSelected);
|
||||
}
|
||||
|
||||
ImGui.SetCursorScreenPos(treeP0 with { Y = p1.Y });
|
||||
if (ImGui.Selectable(name, s.Renderer.Options.SelectedAnimation == i, ImGuiSelectableFlags.SpanAllColumns, new Vector2(p1.X - treeP0.X, timeStep.Y - t - t)))
|
||||
{
|
||||
s.Renderer.Options.SelectAnimation(i);
|
||||
}
|
||||
Popup(s, i);
|
||||
}
|
||||
|
||||
private void Popup(Snooper s, int i)
|
||||
{
|
||||
SnimGui.Popup(() =>
|
||||
{
|
||||
s.Renderer.Options.SelectAnimation(i);
|
||||
if (ImGui.BeginMenu("Animate"))
|
||||
{
|
||||
foreach ((var guid, var model) in s.Renderer.Options.Models)
|
||||
{
|
||||
var selected = AttachedModels.Contains(guid);
|
||||
if (model is SkeletalModel skeletalModel && ImGui.MenuItem(model.Name, null, selected, !skeletalModel.Skeleton.IsAnimated || selected))
|
||||
{
|
||||
if (selected) AttachedModels.Remove(guid); else AttachedModels.Add(guid);
|
||||
skeletalModel.Skeleton.ResetAnimatedData(true);
|
||||
if (!selected) skeletalModel.Skeleton.Animate(UnrealAnim);
|
||||
}
|
||||
}
|
||||
ImGui.EndMenu();
|
||||
}
|
||||
if (ImGui.MenuItem("Save"))
|
||||
{
|
||||
s.WindowShouldFreeze(true);
|
||||
ExportModal.Instance.Export([this], UserSettings.Default.ModelDirectory, UserSettings.GetExportOptions());
|
||||
s.WindowShouldFreeze(false);
|
||||
}
|
||||
ImGui.Separator();
|
||||
if (ImGui.MenuItem("Copy Path to Clipboard")) ImGui.SetClipboardText(Path);
|
||||
});
|
||||
}
|
||||
|
||||
public void AddToExportSession(ExportSession session)
|
||||
{
|
||||
session.Add(_export);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FModel.Views.Snooper.Animations;
|
||||
|
||||
public class Bone
|
||||
{
|
||||
public readonly int Index;
|
||||
public int ParentIndex;
|
||||
public readonly Transform Rest;
|
||||
public readonly bool IsVirtual;
|
||||
|
||||
public string LoweredParentName;
|
||||
public List<string> LoweredChildNames;
|
||||
|
||||
public int SkeletonIndex = -1;
|
||||
public readonly List<int> AnimatedBySequences;
|
||||
|
||||
public Bone(int i, int p, Transform t, bool isVirtual = false)
|
||||
{
|
||||
Index = i;
|
||||
ParentIndex = p;
|
||||
Rest = t;
|
||||
IsVirtual = isVirtual;
|
||||
|
||||
LoweredChildNames = new List<string>();
|
||||
AnimatedBySequences = new List<int>();
|
||||
}
|
||||
|
||||
public bool IsRoot => Index == 0 && ParentIndex == -1 && string.IsNullOrEmpty(LoweredParentName);
|
||||
public bool IsDaron => LoweredChildNames.Count > 0;
|
||||
public bool IsMapped => SkeletonIndex > -1;
|
||||
public bool IsAnimated => AnimatedBySequences.Count > 0;
|
||||
public bool IsNative => Index == SkeletonIndex;
|
||||
public uint Color => !IsMapped || !IsAnimated ? 0xFFA0A0A0 : 0xFF48B048;
|
||||
|
||||
public override string ToString() => $"Mesh Ref '{Index}' is Skel Ref '{SkeletonIndex}'";
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
using System.Numerics;
|
||||
using CUE4Parse_Conversion.Writers.ActorX.Structs.Animations;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace FModel.Views.Snooper.Animations;
|
||||
|
||||
public class Sequence
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly float RateScale;
|
||||
public readonly float StartTime;
|
||||
public readonly float Duration;
|
||||
public readonly float EndTime;
|
||||
public readonly int EndFrame;
|
||||
public readonly int LoopingCount;
|
||||
public readonly bool IsAdditive;
|
||||
public readonly float SecondsPerFrame;
|
||||
|
||||
public Sequence(CAnimSequence sequence)
|
||||
{
|
||||
Name = sequence.Name;
|
||||
RateScale = sequence.OriginalSequence.RateScale;
|
||||
StartTime = sequence.StartPos;
|
||||
Duration = sequence.AnimEndTime;
|
||||
EndTime = StartTime + Duration / RateScale;
|
||||
EndFrame = sequence.NumFrames;
|
||||
LoopingCount = sequence.LoopingCount;
|
||||
IsAdditive = sequence.IsAdditive;
|
||||
SecondsPerFrame = Duration / EndFrame;
|
||||
}
|
||||
|
||||
public void DrawSequence(ImDrawListPtr drawList, ImFontPtr fontPtr, float x, Vector2 p2, Vector2 timeStep, Vector2 timeRatio, float t, bool animSelected)
|
||||
{
|
||||
var halfThickness = t / 2.0f;
|
||||
var q1 = new Vector2(x + StartTime * timeRatio.X + t + halfThickness, p2.Y - timeStep.Y / 2.0f);
|
||||
var q2 = p2 with { X = x + EndTime * timeRatio.X - t * 2.0f };
|
||||
|
||||
drawList.PushClipRect(q1, q2 with { X = q2.X + t }, true);
|
||||
|
||||
var lineColor = animSelected ? 0xA0FFFFFF : 0x50FFFFFF;
|
||||
drawList.AddLine(new Vector2(q1.X, q2.Y), q1, lineColor, 1.0f);
|
||||
drawList.AddLine(q1, new Vector2(q2.X, q1.Y), lineColor, 1.0f);
|
||||
drawList.AddLine(new Vector2(q2.X, q1.Y), q2, lineColor, 1.0f);
|
||||
|
||||
if (IsAdditive)
|
||||
drawList.AddText(fontPtr, 12 * ImGui.GetWindowDpiScale(), new Vector2(q1.X + t, q1.Y + halfThickness), animSelected ? 0xFFFFFFFF : 0x50FFFFFF, "Is Additive");
|
||||
|
||||
drawList.PopClipRect();
|
||||
}
|
||||
}
|
||||
@@ -1,393 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using CUE4Parse_Conversion.Writers.ActorX.Structs.Animations;
|
||||
using CUE4Parse.UE4.Assets.Exports.Animation;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using FModel.Views.Snooper.Buffers;
|
||||
using FModel.Views.Snooper.Shading;
|
||||
using ImGuiNET;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using Serilog;
|
||||
|
||||
namespace FModel.Views.Snooper.Animations;
|
||||
|
||||
public class Skeleton : IDisposable
|
||||
{
|
||||
private int _handle;
|
||||
private BufferObject<Matrix4x4> _rest;
|
||||
private BufferObject<Matrix4x4> _ssbo;
|
||||
private Matrix4x4[] _boneMatriceAtFrame;
|
||||
private readonly List<string> _breadcrumb;
|
||||
|
||||
public string Name;
|
||||
public FGuid Guid;
|
||||
public readonly Dictionary<string, Bone> BonesByLoweredName;
|
||||
|
||||
public readonly int BoneCount;
|
||||
private int _additionalBoneCount;
|
||||
private int TotalBoneCount => BoneCount + _additionalBoneCount;
|
||||
|
||||
public bool IsAnimated { get; private set; }
|
||||
public string SelectedBone;
|
||||
|
||||
private const int _vertexSize = 12;
|
||||
private BufferObject<float> _vbo;
|
||||
private int _vaoHandle;
|
||||
|
||||
public Skeleton()
|
||||
{
|
||||
BonesByLoweredName = new Dictionary<string, Bone>();
|
||||
_breadcrumb = new List<string>();
|
||||
}
|
||||
|
||||
public Skeleton(FReferenceSkeleton referenceSkeleton) : this()
|
||||
{
|
||||
BoneCount = referenceSkeleton.FinalRefBoneInfo.Length;
|
||||
for (int boneIndex = 0; boneIndex < BoneCount; boneIndex++)
|
||||
{
|
||||
var info = referenceSkeleton.FinalRefBoneInfo[boneIndex];
|
||||
var boneName = info.Name.Text.ToLower();
|
||||
var bone = new Bone(boneIndex, info.ParentIndex, new Transform
|
||||
{
|
||||
Rotation = referenceSkeleton.FinalRefBonePose[boneIndex].Rotation,
|
||||
Position = referenceSkeleton.FinalRefBonePose[boneIndex].Translation * Constants.SCALE_DOWN_RATIO,
|
||||
Scale = referenceSkeleton.FinalRefBonePose[boneIndex].Scale3D
|
||||
});
|
||||
|
||||
if (!bone.IsRoot)
|
||||
{
|
||||
bone.LoweredParentName = referenceSkeleton.FinalRefBoneInfo[bone.ParentIndex].Name.Text.ToLower();
|
||||
var parentBone = BonesByLoweredName[bone.LoweredParentName];
|
||||
|
||||
bone.Rest.Relation = parentBone.Rest.Matrix;
|
||||
parentBone.LoweredChildNames.Add(boneName);
|
||||
}
|
||||
else bone.Rest.Scale = FVector.OneVector;
|
||||
|
||||
if (boneIndex == 0) SelectedBone = boneName;
|
||||
BonesByLoweredName[boneName] = bone;
|
||||
}
|
||||
_breadcrumb.Add(SelectedBone);
|
||||
_boneMatriceAtFrame = new Matrix4x4[BoneCount];
|
||||
}
|
||||
|
||||
public void Merge(FReferenceSkeleton referenceSkeleton)
|
||||
{
|
||||
for (int boneIndex = 0; boneIndex < referenceSkeleton.FinalRefBoneInfo.Length; boneIndex++)
|
||||
{
|
||||
var info = referenceSkeleton.FinalRefBoneInfo[boneIndex];
|
||||
var boneName = info.Name.Text.ToLower();
|
||||
|
||||
if (!BonesByLoweredName.TryGetValue(boneName, out var bone))
|
||||
{
|
||||
bone = new Bone(BoneCount + _additionalBoneCount, -1, new Transform
|
||||
{
|
||||
Rotation = referenceSkeleton.FinalRefBonePose[boneIndex].Rotation,
|
||||
Position = referenceSkeleton.FinalRefBonePose[boneIndex].Translation * Constants.SCALE_DOWN_RATIO,
|
||||
Scale = referenceSkeleton.FinalRefBonePose[boneIndex].Scale3D
|
||||
}, true);
|
||||
|
||||
if (!bone.IsRoot)
|
||||
{
|
||||
bone.LoweredParentName = referenceSkeleton.FinalRefBoneInfo[info.ParentIndex].Name.Text.ToLower();
|
||||
var parentBone = BonesByLoweredName[bone.LoweredParentName];
|
||||
|
||||
bone.ParentIndex = parentBone.Index;
|
||||
bone.Rest.Relation = parentBone.Rest.Matrix;
|
||||
parentBone.LoweredChildNames.Add(boneName);
|
||||
}
|
||||
|
||||
BonesByLoweredName[boneName] = bone;
|
||||
_additionalBoneCount++;
|
||||
}
|
||||
}
|
||||
_boneMatriceAtFrame = new Matrix4x4[TotalBoneCount];
|
||||
}
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
_handle = GL.CreateProgram();
|
||||
|
||||
_vaoHandle = GL.GenVertexArray();
|
||||
GL.BindVertexArray(_vaoHandle);
|
||||
|
||||
_vbo = new BufferObject<float>(_vertexSize * BoneCount, BufferTarget.ArrayBuffer);
|
||||
|
||||
var sf = sizeof(float);
|
||||
var half = _vertexSize / 2;
|
||||
GL.EnableVertexAttribArray(0);
|
||||
GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, sf * half, sf * 0);
|
||||
GL.EnableVertexAttribArray(1);
|
||||
GL.VertexAttribPointer(1, 3, VertexAttribPointerType.Float, false, sf * half, sf * 3);
|
||||
|
||||
GL.BindVertexArray(0);
|
||||
|
||||
_rest = new BufferObject<Matrix4x4>(BoneCount, BufferTarget.ShaderStorageBuffer);
|
||||
foreach (var bone in BonesByLoweredName.Values)
|
||||
{
|
||||
if (bone.IsVirtual) break;
|
||||
_rest.Update(bone.Index, bone.Rest.Matrix);
|
||||
}
|
||||
_rest.Unbind();
|
||||
|
||||
_ssbo = new BufferObject<Matrix4x4>(TotalBoneCount, BufferTarget.ShaderStorageBuffer);
|
||||
_ssbo.UpdateRange(Matrix4x4.Identity);
|
||||
}
|
||||
|
||||
public void Animate(CAnimSet animation)
|
||||
{
|
||||
IsAnimated = true;
|
||||
ResetAnimatedData();
|
||||
|
||||
// map bones
|
||||
for (int boneIndex = 0; boneIndex < animation.Skeleton.BoneCount; boneIndex++)
|
||||
{
|
||||
var info = animation.Skeleton.ReferenceSkeleton.FinalRefBoneInfo[boneIndex];
|
||||
if (!BonesByLoweredName.TryGetValue(info.Name.Text.ToLower(), out var bone))
|
||||
continue;
|
||||
|
||||
bone.SkeletonIndex = boneIndex;
|
||||
}
|
||||
|
||||
// find playable sequences
|
||||
for (int s = 0; s < animation.Sequences.Count; s++)
|
||||
{
|
||||
var sequence = animation.Sequences[s];
|
||||
foreach (var bone in BonesByLoweredName.Values.Where(bone => sequence.OriginalSequence.FindTrackForBoneIndex(bone.SkeletonIndex) >= 0))
|
||||
{
|
||||
bone.AnimatedBySequences.Add(s);
|
||||
}
|
||||
sequence.RetargetTracks(animation.Skeleton);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
foreach ((var boneName, var bone) in BonesByLoweredName)
|
||||
{
|
||||
if (bone.IsRoot || bone.IsMapped) // assuming root bone always is mapped
|
||||
continue;
|
||||
|
||||
Log.Warning($"{Name} Bone Mismatch: {boneName} ({bone.Index}) was not present in the anim's target skeleton");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public void ResetAnimatedData(bool full = false)
|
||||
{
|
||||
foreach (var bone in BonesByLoweredName.Values)
|
||||
{
|
||||
bone.SkeletonIndex = -1;
|
||||
bone.AnimatedBySequences.Clear();
|
||||
}
|
||||
|
||||
if (!full) return;
|
||||
IsAnimated = false;
|
||||
_ssbo.UpdateRange(Matrix4x4.Identity);
|
||||
}
|
||||
|
||||
public void UpdateAnimationMatrices(Animation animation, bool rotationOnly)
|
||||
{
|
||||
if (!IsAnimated) return;
|
||||
|
||||
_ssbo.Bind();
|
||||
foreach (var bone in BonesByLoweredName.Values)
|
||||
{
|
||||
var boneMatrix = bone.IsRoot ? bone.Rest.Relation : bone.Rest.LocalMatrix * _boneMatriceAtFrame[bone.ParentIndex];
|
||||
if (bone.IsAnimated)
|
||||
{
|
||||
var (s, f) = GetBoneFrameData(bone, animation);
|
||||
var sequence = animation.UnrealAnim.Sequences[s];
|
||||
var boneOrientation = bone.Rest.Rotation;
|
||||
var bonePosition = bone.Rest.Position;
|
||||
var boneScale = bone.Rest.Scale;
|
||||
|
||||
sequence.Tracks[bone.SkeletonIndex].GetBoneTransform(f, sequence.NumFrames, ref boneOrientation, ref bonePosition, ref boneScale);
|
||||
if (!bone.IsRoot) boneMatrix = _boneMatriceAtFrame[bone.ParentIndex];
|
||||
else boneScale = bone.Rest.Scale;
|
||||
bonePosition = rotationOnly ? bone.Rest.Position : bonePosition * Constants.SCALE_DOWN_RATIO;
|
||||
|
||||
boneMatrix = new Transform
|
||||
{
|
||||
Relation = boneMatrix,
|
||||
Rotation = boneOrientation,
|
||||
Position = bonePosition,
|
||||
Scale = boneScale
|
||||
}.Matrix;
|
||||
}
|
||||
|
||||
_ssbo.Update(bone.Index, boneMatrix);
|
||||
_boneMatriceAtFrame[bone.Index] = boneMatrix;
|
||||
}
|
||||
_ssbo.Unbind();
|
||||
}
|
||||
|
||||
public void UpdateVertices()
|
||||
{
|
||||
_vbo.Bind();
|
||||
foreach (var (boneName, bone) in BonesByLoweredName)
|
||||
{
|
||||
var boneMatrix = IsAnimated ? _boneMatriceAtFrame[bone.Index] : bone.Rest.Matrix;
|
||||
var parentBoneMatrix = bone.IsRoot ? boneMatrix :
|
||||
IsAnimated ? _boneMatriceAtFrame[bone.ParentIndex] :
|
||||
BonesByLoweredName[bone.LoweredParentName].Rest.Matrix;
|
||||
|
||||
var count = 0;
|
||||
var baseIndex = bone.Index * _vertexSize;
|
||||
_vbo.Update(baseIndex + count++, boneMatrix.Translation.X);
|
||||
_vbo.Update(baseIndex + count++, boneMatrix.Translation.Y);
|
||||
_vbo.Update(baseIndex + count++, boneMatrix.Translation.Z);
|
||||
_vbo.Update(baseIndex + count++, 1.0f);
|
||||
_vbo.Update(baseIndex + count++, boneName == SelectedBone ? 0.0f : 1.0f);
|
||||
_vbo.Update(baseIndex + count++, boneName == SelectedBone ? 0.0f : 1.0f);
|
||||
_vbo.Update(baseIndex + count++, parentBoneMatrix.Translation.X);
|
||||
_vbo.Update(baseIndex + count++, parentBoneMatrix.Translation.Y);
|
||||
_vbo.Update(baseIndex + count++, parentBoneMatrix.Translation.Z);
|
||||
_vbo.Update(baseIndex + count++, 1.0f);
|
||||
_vbo.Update(baseIndex + count++, bone.LoweredParentName == SelectedBone ? 0.0f : 1.0f);
|
||||
_vbo.Update(baseIndex + count++, bone.LoweredParentName == SelectedBone ? 0.0f : 1.0f);
|
||||
}
|
||||
_vbo.Unbind();
|
||||
}
|
||||
|
||||
private (int, float) GetBoneFrameData(Bone bone, Animation animation)
|
||||
{
|
||||
int s = -1;
|
||||
float f = 0.0f;
|
||||
|
||||
void Get(Bone b)
|
||||
{
|
||||
foreach (var i in b.AnimatedBySequences)
|
||||
{
|
||||
s = i;
|
||||
if (animation.Framing.TryGetValue(s, out f))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Get(bone);
|
||||
if (s == -1)
|
||||
{
|
||||
var parent = BonesByLoweredName[bone.LoweredParentName];
|
||||
while (!parent.IsAnimated)
|
||||
{
|
||||
parent = BonesByLoweredName[parent.LoweredParentName];
|
||||
}
|
||||
Get(parent);
|
||||
}
|
||||
|
||||
return (s, f);
|
||||
}
|
||||
|
||||
public Matrix4x4 GetBoneMatrix(Bone bone) => IsAnimated ? _boneMatriceAtFrame[bone.Index] : bone.Rest.Matrix;
|
||||
|
||||
public void Render(Shader shader)
|
||||
{
|
||||
shader.SetUniform("uIsAnimated", IsAnimated);
|
||||
|
||||
_ssbo.BindBufferBase(1);
|
||||
_rest.BindBufferBase(2);
|
||||
}
|
||||
|
||||
public void RenderBones()
|
||||
{
|
||||
GL.Disable(EnableCap.DepthTest);
|
||||
|
||||
GL.BindVertexArray(_vaoHandle);
|
||||
GL.DrawArrays(PrimitiveType.Lines, 0, _vbo.Size);
|
||||
GL.DrawArrays(PrimitiveType.Points, 0, _vbo.Size);
|
||||
GL.BindVertexArray(0);
|
||||
|
||||
GL.Enable(EnableCap.DepthTest);
|
||||
}
|
||||
|
||||
public void ImGuiBoneBreadcrumb()
|
||||
{
|
||||
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 >= 0; i--)
|
||||
{
|
||||
var boneName = _breadcrumb[i];
|
||||
var size = ImGui.CalcTextSize(boneName);
|
||||
var position = new Vector2(x + 5, y - size.Y / 2f);
|
||||
|
||||
ImGui.SetCursorScreenPos(position);
|
||||
if (ImGui.InvisibleButton($"breakfast_{boneName}", size, ImGuiButtonFlags.MouseButtonLeft))
|
||||
{
|
||||
SelectedBone = boneName;
|
||||
_breadcrumb.RemoveRange(0, i);
|
||||
break;
|
||||
}
|
||||
|
||||
drawList.AddText(position, i == 0 || 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()
|
||||
{
|
||||
foreach (var name in BonesByLoweredName[SelectedBone].LoweredChildNames)
|
||||
{
|
||||
DrawBoneTree(name, BonesByLoweredName[name]);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawBoneTree(string boneName, Bone bone)
|
||||
{
|
||||
ImGui.PushID(bone.Index);
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
|
||||
var flags = ImGuiTreeNodeFlags.OpenOnArrow | ImGuiTreeNodeFlags.SpanFullWidth;
|
||||
if (bone.IsVirtual) flags |= ImGuiTreeNodeFlags.Leaf;
|
||||
else if (!bone.IsDaron) flags |= ImGuiTreeNodeFlags.Bullet;
|
||||
|
||||
ImGui.SetNextItemOpen(bone.LoweredChildNames.Count <= 1, ImGuiCond.Appearing);
|
||||
var open = ImGui.TreeNodeEx(boneName, flags);
|
||||
if (ImGui.IsItemClicked() && !ImGui.IsItemToggledOpen() && bone.IsDaron)
|
||||
{
|
||||
SelectedBone = boneName;
|
||||
_breadcrumb.Clear();
|
||||
do
|
||||
{
|
||||
_breadcrumb.Add(boneName);
|
||||
boneName = BonesByLoweredName[boneName].LoweredParentName;
|
||||
} while (boneName != null);
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextColored(ImGui.ColorConvertU32ToFloat4(bone.Color), bone.SkeletonIndex.ToString());
|
||||
|
||||
if (open)
|
||||
{
|
||||
foreach (var name in bone.LoweredChildNames)
|
||||
{
|
||||
DrawBoneTree(name, BonesByLoweredName[name]);
|
||||
}
|
||||
ImGui.TreePop();
|
||||
}
|
||||
ImGui.PopID();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
BonesByLoweredName.Clear();
|
||||
|
||||
_rest?.Dispose();
|
||||
_ssbo?.Dispose();
|
||||
GL.DeleteProgram(_handle);
|
||||
}
|
||||
}
|
||||
@@ -1,226 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using FModel.Views.Snooper.Shading;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace FModel.Views.Snooper.Animations;
|
||||
|
||||
public enum ETrackerType
|
||||
{
|
||||
Start,
|
||||
Frame,
|
||||
InBetween,
|
||||
End
|
||||
}
|
||||
|
||||
public class TimeTracker : IDisposable
|
||||
{
|
||||
public bool IsPaused;
|
||||
public bool IsActive;
|
||||
public float ElapsedTime;
|
||||
public float MaxElapsedTime;
|
||||
public float TimeMultiplier;
|
||||
|
||||
public TimeTracker()
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
public void Update(float deltaSeconds)
|
||||
{
|
||||
if (IsPaused || IsActive) return;
|
||||
ElapsedTime += deltaSeconds * TimeMultiplier;
|
||||
if (ElapsedTime >= MaxElapsedTime) Reset(false);
|
||||
}
|
||||
|
||||
public void SafeSetElapsedTime(float elapsedTime)
|
||||
{
|
||||
ElapsedTime = Math.Clamp(elapsedTime, 0.0f, MaxElapsedTime);
|
||||
}
|
||||
|
||||
public void SafeSetMaxElapsedTime(float maxElapsedTime)
|
||||
{
|
||||
MaxElapsedTime = MathF.Max(maxElapsedTime, MaxElapsedTime);
|
||||
}
|
||||
|
||||
public void Reset(bool doMet = true)
|
||||
{
|
||||
IsPaused = false;
|
||||
ElapsedTime = 0.0f;
|
||||
if (doMet)
|
||||
{
|
||||
MaxElapsedTime = 0.01f;
|
||||
TimeMultiplier = 1f;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
private readonly string[] _icons = { "tl_forward", "tl_pause", "tl_rewind" };
|
||||
public void ImGuiTimeline(Snooper s, Dictionary<string, Texture> icons, List<Animation> animations, Vector2 outliner, ImFontPtr fontPtr)
|
||||
{
|
||||
var dpiScale = ImGui.GetWindowDpiScale();
|
||||
var thickness = 2.0f * dpiScale;
|
||||
var buttonWidth = 14.0f * dpiScale;
|
||||
var timeHeight = 10.0f * dpiScale;
|
||||
var timeBarHeight = timeHeight * 2.0f;
|
||||
var timeStep = new Vector2(50 * dpiScale, 25 * dpiScale);
|
||||
|
||||
var treeP0 = ImGui.GetCursorScreenPos();
|
||||
var canvasSize = ImGui.GetContentRegionAvail();
|
||||
var canvasMaxY = MathF.Max(canvasSize.Y, timeBarHeight + timeStep.Y * animations.Count);
|
||||
ImGui.BeginChild("timeline_child", canvasSize with { Y = canvasMaxY });
|
||||
|
||||
var timelineP1 = new Vector2(treeP0.X + canvasSize.X, treeP0.Y + canvasMaxY);
|
||||
var treeP1 = timelineP1 with { X = treeP0.X + outliner.X };
|
||||
|
||||
var timelineP0 = treeP0 with { X = treeP1.X + thickness };
|
||||
var timelineSize = timelineP1 - timelineP0;
|
||||
var timeRatio = timelineSize / MaxElapsedTime;
|
||||
|
||||
var drawList = ImGui.GetWindowDrawList();
|
||||
drawList.PushClipRect(treeP0, timelineP1, true);
|
||||
drawList.AddRectFilled(treeP0, treeP1, 0xFF1F1C1C);
|
||||
drawList.AddRectFilled(timelineP0, timelineP1 with { Y = timelineP0.Y + timeBarHeight }, 0xFF141414);
|
||||
drawList.AddRectFilled(timelineP0 with { Y = timelineP0.Y + timeBarHeight }, timelineP1, 0xFF242424);
|
||||
drawList.AddLine(new Vector2(treeP1.X, treeP0.Y), treeP1, 0xFF504545, thickness);
|
||||
drawList.AddLine(treeP0 with { Y = timelineP0.Y + timeBarHeight }, timelineP1 with { Y = timelineP0.Y + timeBarHeight }, 0x50504545, thickness);
|
||||
|
||||
// adding margin
|
||||
var margin = 5.0f * dpiScale;
|
||||
treeP0.X += margin;
|
||||
treeP1.X -= margin;
|
||||
|
||||
// control buttons
|
||||
for (int i = 0; i < _icons.Length; i++)
|
||||
{
|
||||
var x = buttonWidth * 2.0f * i;
|
||||
ImGui.SetCursorScreenPos(treeP0 with { X = treeP1.X - x - buttonWidth * 2.0f + thickness });
|
||||
if (ImGui.ImageButton($"timeline_actions_{_icons[i]}", icons[i == 1 ? IsPaused ? "tl_play" : "tl_pause" : _icons[i]].GetPointer(), new Vector2(buttonWidth)))
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case 0:
|
||||
SafeSetElapsedTime(ElapsedTime + timeStep.X / timeRatio.X);
|
||||
break;
|
||||
case 1:
|
||||
IsPaused = !IsPaused;
|
||||
break;
|
||||
case 2:
|
||||
SafeSetElapsedTime(ElapsedTime - timeStep.X / timeRatio.X);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drawList.AddText(treeP0 with { Y = treeP0.Y + thickness }, 0xA0FFFFFF, $"{ElapsedTime:F1}/{MaxElapsedTime:F1} seconds");
|
||||
|
||||
ImGui.SetCursorScreenPos(timelineP0);
|
||||
ImGui.InvisibleButton("timeline_timetracker_canvas", timelineSize with { Y = timeBarHeight }, ImGuiButtonFlags.MouseButtonLeft);
|
||||
IsActive = ImGui.IsItemActive();
|
||||
if (IsActive && ImGui.IsMouseDragging(ImGuiMouseButton.Left))
|
||||
{
|
||||
var mousePosCanvas = ImGui.GetIO().MousePos - timelineP0;
|
||||
SafeSetElapsedTime(mousePosCanvas.X / timelineSize.X * MaxElapsedTime);
|
||||
foreach (var animation in animations)
|
||||
{
|
||||
animation.TimeCalculation(ElapsedTime);
|
||||
}
|
||||
}
|
||||
|
||||
{ // draw time + time grid
|
||||
for (float x = 0; x < timelineSize.X; x += timeStep.X)
|
||||
{
|
||||
var cursor = timelineP0.X + x;
|
||||
drawList.AddLine(new Vector2(cursor, timelineP0.Y + timeHeight + 2.5f), new Vector2(cursor, timelineP0.Y + timeBarHeight), 0xA0FFFFFF);
|
||||
drawList.AddLine(new Vector2(cursor, timelineP0.Y + timeBarHeight), timelineP1 with { X = cursor }, 0x28C8C8C8);
|
||||
drawList.AddText(fontPtr, 14 * dpiScale, new Vector2(cursor + 4, timelineP0.Y + 7.5f), 0x50FFFFFF, $"{x / timeRatio.X:F1}s");
|
||||
}
|
||||
|
||||
for (float y = timeBarHeight; y < timelineSize.Y; y += timeStep.Y)
|
||||
{
|
||||
drawList.AddLine(timelineP0 with { Y = timelineP0.Y + y }, timelineP1 with { Y = timelineP0.Y + y }, 0x28C8C8C8);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.SelectableTextAlign, new Vector2(0.0f, 0.5f));
|
||||
for (int i = 0; i < animations.Count; i++)
|
||||
{
|
||||
var y = timelineP0.Y + timeBarHeight + timeStep.Y * i;
|
||||
animations[i].ImGuiAnimation(s, drawList, fontPtr, timelineP0, treeP0, timeStep, timeRatio, y, thickness, i);
|
||||
DrawSeparator(drawList, timelineP0, y + timeStep.Y, animations[i].EndTime * timeRatio.X, timeHeight, timeBarHeight, ETrackerType.End);
|
||||
}
|
||||
ImGui.PopStyleVar();
|
||||
|
||||
for (int i = 0; i < animations.Count; i++)
|
||||
{
|
||||
var y = timelineP0.Y + timeBarHeight + timeStep.Y * i;
|
||||
for (int j = 0; j < animations[i].Sequences.Length - 1; j++)
|
||||
{
|
||||
DrawSeparator(drawList, timelineP0, y + timeStep.Y - thickness, animations[i].Sequences[j].EndTime * timeRatio.X - 0.5f, timeHeight, timeBarHeight, ETrackerType.InBetween);
|
||||
}
|
||||
}
|
||||
|
||||
DrawSeparator(drawList, timelineP0, timelineP1.Y, ElapsedTime * timeRatio.X, timeHeight, timeBarHeight, ETrackerType.Frame);
|
||||
|
||||
drawList.PopClipRect();
|
||||
ImGui.EndChild();
|
||||
}
|
||||
|
||||
private void DrawSeparator(ImDrawListPtr drawList, Vector2 origin, float y, float time, float timeHeight, float timeBarHeight, ETrackerType separatorType)
|
||||
{
|
||||
float size = separatorType switch
|
||||
{
|
||||
ETrackerType.Frame => 5,
|
||||
ETrackerType.End => 5,
|
||||
ETrackerType.InBetween => 7.5f,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(separatorType), separatorType, null)
|
||||
};
|
||||
|
||||
Vector2 p1 = separatorType switch
|
||||
{
|
||||
ETrackerType.Frame => new Vector2(origin.X + time, origin.Y + timeBarHeight),
|
||||
ETrackerType.End => origin with { X = origin.X + time },
|
||||
ETrackerType.InBetween => origin with { X = origin.X + time },
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(separatorType), separatorType, null)
|
||||
};
|
||||
var p2 = p1 with { Y = y };
|
||||
|
||||
uint color = separatorType switch
|
||||
{
|
||||
ETrackerType.Frame => 0xFF6F6F6F,
|
||||
ETrackerType.End => 0xFF2E3E82,
|
||||
ETrackerType.InBetween => 0xA0FFFFFF,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(separatorType), separatorType, null)
|
||||
};
|
||||
|
||||
switch (separatorType)
|
||||
{
|
||||
case ETrackerType.Frame:
|
||||
color = 0xFF30478C;
|
||||
var xl = p1.X - size;
|
||||
var xr = p1.X + size;
|
||||
var yb = origin.Y + timeBarHeight - timeHeight / 2.0f;
|
||||
|
||||
drawList.AddLine(p1, p2, color, 1f);
|
||||
drawList.AddQuadFilled(origin with { X = xl }, origin with { X = xr }, new Vector2(xr, yb), new Vector2(xl, yb), color);
|
||||
drawList.AddTriangleFilled(new Vector2(xl, yb), new Vector2(xr, yb), p1, color);
|
||||
break;
|
||||
case ETrackerType.End:
|
||||
drawList.AddLine(p1, p2, color, 1f);
|
||||
drawList.AddTriangleFilled(p1, p1 with { X = p1.X - size }, p1 with { Y = p1.Y + size }, color);
|
||||
break;
|
||||
case ETrackerType.InBetween:
|
||||
p1.Y += timeBarHeight;
|
||||
drawList.AddLine(p1, p2, color, 1f);
|
||||
drawList.AddTriangleFilled(p1, new Vector2(p1.X - size / 2.0f, p1.Y - size), new Vector2(p1.X + size / 2.0f, p1.Y - size), color);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(separatorType), separatorType, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
using System;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
|
||||
namespace FModel.Views.Snooper.Buffers;
|
||||
|
||||
public class BufferObject<TDataType> : IDisposable where TDataType : unmanaged
|
||||
{
|
||||
private readonly int _handle;
|
||||
private readonly int _sizeOf;
|
||||
private readonly BufferTarget _bufferTarget;
|
||||
|
||||
public readonly int Size;
|
||||
|
||||
private unsafe BufferObject(BufferTarget bufferTarget)
|
||||
{
|
||||
_bufferTarget = bufferTarget;
|
||||
_handle = GL.GenBuffer();
|
||||
_sizeOf = sizeof(TDataType);
|
||||
|
||||
Bind();
|
||||
}
|
||||
|
||||
public BufferObject(TDataType[] data, BufferTarget bufferTarget) : this(bufferTarget)
|
||||
{
|
||||
Size = data.Length;
|
||||
GL.BufferData(bufferTarget, Size * _sizeOf, data, BufferUsageHint.StaticDraw);
|
||||
}
|
||||
|
||||
public BufferObject(int length, BufferTarget bufferTarget) : this(bufferTarget)
|
||||
{
|
||||
Size = length;
|
||||
GL.BufferData(bufferTarget, Size * _sizeOf, IntPtr.Zero, BufferUsageHint.DynamicDraw);
|
||||
}
|
||||
|
||||
public void UpdateRange(TDataType data) => UpdateRange(Size, data);
|
||||
public void UpdateRange(int count, TDataType data)
|
||||
{
|
||||
Bind();
|
||||
for (int i = 0; i < count; i++) Update(i, data);
|
||||
Unbind();
|
||||
}
|
||||
|
||||
public void Update(int offset, TDataType data)
|
||||
{
|
||||
GL.BufferSubData(_bufferTarget, (IntPtr) (offset * _sizeOf), _sizeOf, ref data);
|
||||
}
|
||||
|
||||
public void Update(TDataType[] data)
|
||||
{
|
||||
Bind();
|
||||
GL.BufferSubData(_bufferTarget, IntPtr.Zero, data.Length * _sizeOf, data);
|
||||
Unbind();
|
||||
}
|
||||
|
||||
public TDataType Get(int offset)
|
||||
{
|
||||
TDataType data = default;
|
||||
GL.GetBufferSubData(_bufferTarget, (IntPtr) (offset * _sizeOf), _sizeOf, ref data);
|
||||
return data;
|
||||
}
|
||||
|
||||
public void Bind()
|
||||
{
|
||||
GL.BindBuffer(_bufferTarget, _handle);
|
||||
}
|
||||
|
||||
public void BindBufferBase(int index)
|
||||
{
|
||||
if (_bufferTarget != BufferTarget.ShaderStorageBuffer)
|
||||
throw new ArgumentException("BindBufferBase is not allowed for anything but Shader Storage Buffers");
|
||||
GL.BindBufferBase(BufferRangeTarget.ShaderStorageBuffer, index, _handle);
|
||||
}
|
||||
|
||||
public void Unbind()
|
||||
{
|
||||
GL.BindBuffer(_bufferTarget, 0);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GL.DeleteBuffer(_handle);
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
using System;
|
||||
using FModel.Views.Snooper.Shading;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
|
||||
namespace FModel.Views.Snooper.Buffers;
|
||||
|
||||
public class FramebufferObject : IDisposable
|
||||
{
|
||||
private int _framebufferHandle;
|
||||
private int _postProcessingHandle;
|
||||
|
||||
private int _width;
|
||||
private int _height;
|
||||
private readonly RenderbufferObject _renderbuffer;
|
||||
|
||||
private BufferObject<uint> _ebo;
|
||||
private BufferObject<float> _vbo;
|
||||
private VertexArrayObject<float, uint> _vao;
|
||||
|
||||
private Shader _shader;
|
||||
private Texture _framebufferTexture;
|
||||
private Texture _postProcessingTexture;
|
||||
|
||||
public readonly uint[] Indices = { 0, 1, 2, 3, 4, 5 };
|
||||
public readonly float[] Vertices = {
|
||||
// Coords // texCoords
|
||||
1.0f, -1.0f, 1.0f, 0.0f,
|
||||
-1.0f, -1.0f, 0.0f, 0.0f,
|
||||
-1.0f, 1.0f, 0.0f, 1.0f,
|
||||
|
||||
1.0f, 1.0f, 1.0f, 1.0f,
|
||||
1.0f, -1.0f, 1.0f, 0.0f,
|
||||
-1.0f, 1.0f, 0.0f, 1.0f
|
||||
};
|
||||
|
||||
public FramebufferObject(OpenTK.Mathematics.Vector2i size)
|
||||
{
|
||||
_width = size.X;
|
||||
_height = size.Y;
|
||||
_renderbuffer = new RenderbufferObject(_width, _height);
|
||||
}
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
_framebufferHandle = GL.GenFramebuffer();
|
||||
Bind();
|
||||
|
||||
_framebufferTexture = new Texture((uint) _width, (uint) _height);
|
||||
|
||||
_renderbuffer.Setup();
|
||||
|
||||
_shader = new Shader("framebuffer");
|
||||
_shader.Use();
|
||||
_shader.SetUniform("screenTexture", 0);
|
||||
|
||||
_ebo = new BufferObject<uint>(Indices, BufferTarget.ElementArrayBuffer);
|
||||
_vbo = new BufferObject<float>(Vertices, BufferTarget.ArrayBuffer);
|
||||
_vao = new VertexArrayObject<float, uint>(_vbo, _ebo);
|
||||
|
||||
_vao.VertexAttributePointer(0, 2, VertexAttribPointerType.Float, 4, 0); // position
|
||||
_vao.VertexAttributePointer(1, 2, VertexAttribPointerType.Float, 4, 2); // uv
|
||||
|
||||
var status = GL.CheckFramebufferStatus(FramebufferTarget.Framebuffer);
|
||||
if (status != FramebufferErrorCode.FramebufferComplete)
|
||||
{
|
||||
throw new Exception($"Framebuffer failed to bind with error: {GL.GetProgramInfoLog(_framebufferHandle)}");
|
||||
}
|
||||
|
||||
_postProcessingHandle = GL.GenFramebuffer();
|
||||
Bind(_postProcessingHandle);
|
||||
|
||||
_postProcessingTexture = new Texture(_width, _height);
|
||||
|
||||
status = GL.CheckFramebufferStatus(FramebufferTarget.Framebuffer);
|
||||
if (status != FramebufferErrorCode.FramebufferComplete)
|
||||
{
|
||||
throw new Exception($"Post-Processing framebuffer failed to bind with error: {GL.GetProgramInfoLog(_postProcessingHandle)}");
|
||||
}
|
||||
}
|
||||
|
||||
public void Bind() => Bind(_framebufferHandle);
|
||||
public void Bind(int handle)
|
||||
{
|
||||
GL.BindFramebuffer(FramebufferTarget.Framebuffer, handle);
|
||||
}
|
||||
|
||||
public void BindMsaa()
|
||||
{
|
||||
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, _framebufferHandle);
|
||||
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, _postProcessingHandle);
|
||||
GL.BlitFramebuffer(0, 0, _width, _height, 0, 0, _width, _height, ClearBufferMask.ColorBufferBit, BlitFramebufferFilter.Nearest);
|
||||
GL.Disable(EnableCap.DepthTest);
|
||||
|
||||
_shader.Use();
|
||||
_vao.Bind();
|
||||
|
||||
_postProcessingTexture.Bind(TextureUnit.Texture0);
|
||||
|
||||
GL.DrawArrays(PrimitiveType.Triangles, 0, Indices.Length);
|
||||
GL.Enable(EnableCap.DepthTest);
|
||||
}
|
||||
|
||||
public IntPtr GetPointer() => _postProcessingTexture.GetPointer();
|
||||
|
||||
public void WindowResized(int width, int height)
|
||||
{
|
||||
_width = width;
|
||||
_height = height;
|
||||
|
||||
_renderbuffer.WindowResized(width, height);
|
||||
|
||||
_framebufferTexture.WindowResized(width, height);
|
||||
_postProcessingTexture.WindowResized(width, height);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_vao?.Dispose();
|
||||
_shader?.Dispose();
|
||||
_framebufferTexture?.Dispose();
|
||||
_postProcessingTexture?.Dispose();
|
||||
_renderbuffer?.Dispose();
|
||||
GL.DeleteFramebuffer(_framebufferHandle);
|
||||
GL.DeleteFramebuffer(_postProcessingHandle);
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using System.Numerics;
|
||||
using FModel.Views.Snooper.Models;
|
||||
using FModel.Views.Snooper.Shading;
|
||||
|
||||
namespace FModel.Views.Snooper.Buffers;
|
||||
|
||||
public class PickingTexture : IDisposable
|
||||
{
|
||||
private int _width;
|
||||
private int _height;
|
||||
|
||||
private int _framebufferHandle;
|
||||
|
||||
private Shader _shader;
|
||||
private int _pickingTexture;
|
||||
private int _depthTexture;
|
||||
|
||||
public PickingTexture(int width, int height)
|
||||
{
|
||||
_width = width;
|
||||
_height = height;
|
||||
}
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
_framebufferHandle = GL.GenFramebuffer();
|
||||
Bind();
|
||||
|
||||
_shader = new Shader("picking");
|
||||
|
||||
_pickingTexture = GL.GenTexture();
|
||||
GL.BindTexture(TextureTarget.Texture2D, _pickingTexture);
|
||||
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba32ui, _width, _height, 0, PixelFormat.RgbaInteger, PixelType.UnsignedInt, IntPtr.Zero);
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.Nearest);
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int) TextureMinFilter.Nearest);
|
||||
GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, TextureTarget.Texture2D, _pickingTexture, 0);
|
||||
|
||||
_depthTexture = GL.GenTexture();
|
||||
GL.BindTexture(TextureTarget.Texture2D, _depthTexture);
|
||||
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.DepthComponent, _width, _height, 0, PixelFormat.DepthComponent, PixelType.Float, IntPtr.Zero);
|
||||
GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.DepthAttachment, TextureTarget.Texture2D, _depthTexture, 0);
|
||||
|
||||
var status = GL.CheckFramebufferStatus(FramebufferTarget.Framebuffer);
|
||||
if (status != FramebufferErrorCode.FramebufferComplete)
|
||||
{
|
||||
throw new Exception($"Framebuffer failed to bind with error: {GL.GetProgramInfoLog(_framebufferHandle)}");
|
||||
}
|
||||
|
||||
GL.BindTexture(TextureTarget.Texture2D, 0);
|
||||
Bind(0);
|
||||
}
|
||||
|
||||
public void Render(Matrix4x4 viewMatrix, Matrix4x4 projMatrix, IDictionary<FGuid, IRenderableModel> models)
|
||||
{
|
||||
Bind();
|
||||
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
|
||||
|
||||
_shader.Render(viewMatrix, projMatrix);
|
||||
foreach ((var guid, var model) in models)
|
||||
{
|
||||
_shader.SetUniform("uA", guid.A);
|
||||
_shader.SetUniform("uB", guid.B);
|
||||
_shader.SetUniform("uC", guid.C);
|
||||
_shader.SetUniform("uD", guid.D);
|
||||
|
||||
if (!model.IsVisible) continue;
|
||||
model.PickingRender(_shader);
|
||||
}
|
||||
|
||||
Bind(0);
|
||||
}
|
||||
|
||||
public void Bind() => Bind(_framebufferHandle);
|
||||
public void Bind(int handle)
|
||||
{
|
||||
GL.BindFramebuffer(FramebufferTarget.Framebuffer, handle);
|
||||
}
|
||||
|
||||
public FGuid ReadPixel(Vector2 mousePos, Vector2 windowPos, Vector2 windowSize)
|
||||
{
|
||||
Bind();
|
||||
FGuid pixel = default;
|
||||
|
||||
var scaleX = windowSize.X / _width;
|
||||
var scaleY = windowSize.Y / _height;
|
||||
var x = Convert.ToInt32((mousePos.X - windowPos.X) / scaleX);
|
||||
var y = -Convert.ToInt32((mousePos.Y - windowPos.Y) / scaleY);
|
||||
|
||||
GL.ReadBuffer(ReadBufferMode.ColorAttachment0);
|
||||
GL.ReadPixels(x, y, 1, 1, PixelFormat.RgbaInteger, PixelType.UnsignedInt, ref pixel);
|
||||
GL.ReadBuffer(ReadBufferMode.None);
|
||||
|
||||
Bind(0);
|
||||
return pixel;
|
||||
}
|
||||
|
||||
public IntPtr GetPointer() => (IntPtr) _pickingTexture;
|
||||
|
||||
public void WindowResized(int width, int height)
|
||||
{
|
||||
_width = width;
|
||||
_height = height;
|
||||
|
||||
GL.BindTexture(TextureTarget.Texture2D, _pickingTexture);
|
||||
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba32ui, _width, _height, 0, PixelFormat.RgbaInteger, PixelType.UnsignedInt, IntPtr.Zero);
|
||||
|
||||
GL.BindTexture(TextureTarget.Texture2D, _depthTexture);
|
||||
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.DepthComponent, _width, _height, 0, PixelFormat.DepthComponent, PixelType.Float, IntPtr.Zero);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_shader?.Dispose();
|
||||
GL.DeleteTexture(_pickingTexture);
|
||||
GL.DeleteTexture(_depthTexture);
|
||||
GL.DeleteFramebuffer(_framebufferHandle);
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
using System;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
|
||||
namespace FModel.Views.Snooper.Buffers;
|
||||
|
||||
public class RenderbufferObject : IDisposable
|
||||
{
|
||||
private int _handle;
|
||||
|
||||
private int _width;
|
||||
private int _height;
|
||||
|
||||
public RenderbufferObject(int width, int height)
|
||||
{
|
||||
_width = width;
|
||||
_height = height;
|
||||
}
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
_handle = GL.GenRenderbuffer();
|
||||
|
||||
GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, _handle);
|
||||
GL.RenderbufferStorageMultisample(RenderbufferTarget.Renderbuffer, Constants.SAMPLES_COUNT, RenderbufferStorage.Depth24Stencil8, _width, _height);
|
||||
GL.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferAttachment.DepthStencilAttachment, RenderbufferTarget.Renderbuffer, _handle);
|
||||
}
|
||||
|
||||
public void WindowResized(int width, int height)
|
||||
{
|
||||
_width = width;
|
||||
_height = height;
|
||||
|
||||
GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, _handle);
|
||||
GL.RenderbufferStorageMultisample(RenderbufferTarget.Renderbuffer, Constants.SAMPLES_COUNT, RenderbufferStorage.Depth24Stencil8, _width, _height);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GL.DeleteRenderbuffer(_handle);
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
|
||||
namespace FModel.Views.Snooper.Buffers;
|
||||
|
||||
public class VertexArrayObject<TVertexType, TIndexType> : IDisposable where TVertexType : unmanaged where TIndexType : unmanaged
|
||||
{
|
||||
private readonly int _handle;
|
||||
private readonly int _sizeOfVertex;
|
||||
private readonly int _sizeOfIndex;
|
||||
|
||||
public unsafe VertexArrayObject(BufferObject<TVertexType> vbo, BufferObject<TIndexType> ebo)
|
||||
{
|
||||
_handle = GL.GenVertexArray();
|
||||
_sizeOfVertex = sizeof(TVertexType);
|
||||
_sizeOfIndex = sizeof(TIndexType);
|
||||
|
||||
Bind();
|
||||
vbo.Bind();
|
||||
ebo.Bind();
|
||||
}
|
||||
|
||||
public void VertexAttributePointer(uint index, int count, VertexAttribPointerType type, int vertexSize, int offset)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case VertexAttribPointerType.Int:
|
||||
case VertexAttribPointerType.UnsignedInt:
|
||||
GL.VertexAttribIPointer(index, count, (VertexAttribIntegerType) type, vertexSize * _sizeOfVertex, offset * _sizeOfVertex);
|
||||
break;
|
||||
default:
|
||||
GL.VertexAttribPointer(index, count, type, false, vertexSize * _sizeOfVertex, offset * _sizeOfVertex);
|
||||
break;
|
||||
}
|
||||
GL.EnableVertexAttribArray(index);
|
||||
}
|
||||
|
||||
public void Bind()
|
||||
{
|
||||
GL.BindVertexArray(_handle);
|
||||
}
|
||||
|
||||
public void Unbind()
|
||||
{
|
||||
GL.BindVertexArray(0);
|
||||
}
|
||||
|
||||
public unsafe void BindInstancing()
|
||||
{
|
||||
Bind();
|
||||
|
||||
var size = sizeof(Vector4);
|
||||
GL.EnableVertexAttribArray(9);
|
||||
GL.VertexAttribPointer(9, 4, VertexAttribPointerType.Float, false, 4 * size, 0);
|
||||
GL.EnableVertexAttribArray(10);
|
||||
GL.VertexAttribPointer(10, 4, VertexAttribPointerType.Float, false, 4 * size, 1 * size);
|
||||
GL.EnableVertexAttribArray(11);
|
||||
GL.VertexAttribPointer(11, 4, VertexAttribPointerType.Float, false, 4 * size, 2 * size);
|
||||
GL.EnableVertexAttribArray(12);
|
||||
GL.VertexAttribPointer(12, 4, VertexAttribPointerType.Float, false, 4 * size, 3 * size);
|
||||
|
||||
GL.VertexAttribDivisor(9, 1);
|
||||
GL.VertexAttribDivisor(10, 1);
|
||||
GL.VertexAttribDivisor(11, 1);
|
||||
GL.VertexAttribDivisor(12, 1);
|
||||
|
||||
Unbind();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GL.DeleteVertexArray(_handle);
|
||||
}
|
||||
}
|
||||
@@ -1,207 +0,0 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using FModel.Settings;
|
||||
using ImGuiNET;
|
||||
using OpenTK.Windowing.GraphicsLibraryFramework;
|
||||
|
||||
namespace FModel.Views.Snooper;
|
||||
|
||||
public class Camera
|
||||
{
|
||||
public enum WorldMode
|
||||
{
|
||||
FlyCam,
|
||||
Arcball
|
||||
}
|
||||
|
||||
public Vector3 Position;
|
||||
public Vector3 Direction;
|
||||
public WorldMode Mode;
|
||||
public Vector3 PositionArc => Position - Direction;
|
||||
public Vector3 DirectionArc => Direction - Position;
|
||||
public Vector3 Up => Vector3.UnitY;
|
||||
|
||||
public float Zoom = 60f;
|
||||
public float Speed = 1f;
|
||||
public float Far = 100f;
|
||||
public float Near => 0.01f;
|
||||
public float AspectRatio = 16f / 9f;
|
||||
|
||||
public Camera()
|
||||
{
|
||||
Position = new Vector3(0, 1, 1);
|
||||
Direction = Vector3.Zero;
|
||||
Mode = UserSettings.Default.CameraMode;
|
||||
}
|
||||
|
||||
public void Setup(FBox box) => Teleport(FVector.ZeroVector, box, true);
|
||||
public void Teleport(Vector3 instancePos, FBox box, bool updateAll = false)
|
||||
{
|
||||
box.GetCenterAndExtents(out var center, out var extents);
|
||||
center += new FVector(instancePos.X, instancePos.Z, instancePos.Y);
|
||||
var distance = extents.AbsMax();
|
||||
|
||||
Position = new Vector3(instancePos.X, center.Z, instancePos.Z + distance * 2);
|
||||
Direction = new Vector3(center.X, center.Z, center.Y);
|
||||
if (updateAll)
|
||||
{
|
||||
Far = Math.Max(Far, box.Max.AbsMax() * 50f);
|
||||
Speed = Math.Max(Speed, distance);
|
||||
}
|
||||
}
|
||||
|
||||
public void Modify(Vector2 mouseDelta)
|
||||
{
|
||||
var lookSensitivity = Mode switch
|
||||
{
|
||||
WorldMode.FlyCam => 0.002f,
|
||||
WorldMode.Arcball => 0.003f,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
mouseDelta *= lookSensitivity;
|
||||
|
||||
const float tolerance = 0.001f;
|
||||
var rotationX = Matrix4x4.CreateFromAxisAngle(-Up, mouseDelta.X);
|
||||
switch (Mode)
|
||||
{
|
||||
case WorldMode.FlyCam:
|
||||
{
|
||||
Direction = Vector3.Transform(DirectionArc, rotationX) + Position;
|
||||
|
||||
var right = Vector3.Normalize(Vector3.Cross(Up, DirectionArc));
|
||||
|
||||
var currentPitch = MathF.Acos(Vector3.Dot(DirectionArc, Up) / (DirectionArc.Length() * Up.Length()));
|
||||
var newPitch = currentPitch + mouseDelta.Y;
|
||||
var clampedPitch = Math.Clamp(newPitch, tolerance, MathF.PI - tolerance);
|
||||
var pitchDelta = clampedPitch - currentPitch;
|
||||
|
||||
var rotationY = Matrix4x4.CreateFromAxisAngle(right, pitchDelta);
|
||||
Direction = Vector3.Transform(DirectionArc, rotationY) + Position;
|
||||
break;
|
||||
}
|
||||
case WorldMode.Arcball:
|
||||
{
|
||||
Position = Vector3.Transform(PositionArc, rotationX) + Direction;
|
||||
|
||||
var right = Vector3.Normalize(Vector3.Cross(-Up, PositionArc));
|
||||
|
||||
var currentPitch = MathF.Acos(Vector3.Dot(PositionArc, -Up) / (PositionArc.Length() * Up.Length()));
|
||||
var newPitch = currentPitch + mouseDelta.Y;
|
||||
var clampedPitch = Math.Clamp(newPitch, tolerance, MathF.PI - tolerance);
|
||||
var pitchDelta = clampedPitch - currentPitch;
|
||||
|
||||
var rotationY = Matrix4x4.CreateFromAxisAngle(right, pitchDelta);
|
||||
Position = Vector3.Transform(PositionArc, rotationY) + Direction;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
public void Modify(KeyboardState keyboard, float time)
|
||||
{
|
||||
if (!keyboard.IsAnyKeyDown) return;
|
||||
var multiplier = keyboard.IsKeyDown(Keys.LeftShift) ? 2f : 1f;
|
||||
var moveSpeed = Speed * multiplier * time;
|
||||
var moveAxis = Vector3.Normalize(-PositionArc);
|
||||
var panAxis = Vector3.Normalize(Vector3.Cross(moveAxis, Up));
|
||||
|
||||
switch (Mode)
|
||||
{
|
||||
case WorldMode.FlyCam:
|
||||
{
|
||||
if (keyboard.IsKeyDown(Keys.W)) // forward
|
||||
{
|
||||
var d = moveSpeed * moveAxis;
|
||||
Position += d;
|
||||
Direction += d;
|
||||
}
|
||||
if (keyboard.IsKeyDown(Keys.S)) // backward
|
||||
{
|
||||
var d = moveSpeed * moveAxis;
|
||||
Position -= d;
|
||||
Direction -= d;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WorldMode.Arcball:
|
||||
{
|
||||
if (keyboard.IsKeyDown(Keys.W)) // forward
|
||||
Position += moveSpeed * moveAxis;
|
||||
if (keyboard.IsKeyDown(Keys.S)) // backward
|
||||
Position -= moveSpeed * moveAxis;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
if (keyboard.IsKeyDown(Keys.A)) // left
|
||||
{
|
||||
var d = panAxis * moveSpeed;
|
||||
Position -= d;
|
||||
Direction -= d;
|
||||
}
|
||||
if (keyboard.IsKeyDown(Keys.D)) // right
|
||||
{
|
||||
var d = panAxis * moveSpeed;
|
||||
Position += d;
|
||||
Direction += d;
|
||||
}
|
||||
if (keyboard.IsKeyDown(Keys.E)) // up
|
||||
{
|
||||
var d = moveSpeed * Up;
|
||||
Position += d;
|
||||
Direction += d;
|
||||
}
|
||||
if (keyboard.IsKeyDown(Keys.Q)) // down
|
||||
{
|
||||
var d = moveSpeed * Up;
|
||||
Position -= d;
|
||||
Direction -= d;
|
||||
}
|
||||
|
||||
if (keyboard.IsKeyDown(Keys.C)) // zoom in
|
||||
ModifyZoom(+.5f);
|
||||
if (keyboard.IsKeyDown(Keys.X)) // zoom out
|
||||
ModifyZoom(-.5f);
|
||||
}
|
||||
|
||||
private void ModifyZoom(float zoomAmount)
|
||||
{
|
||||
//We don't want to be able to zoom in too close or too far away so clamp to these values
|
||||
Zoom = Math.Clamp(Zoom - zoomAmount, 1.0f, 89f);
|
||||
}
|
||||
|
||||
public Matrix4x4 GetViewMatrix() => Matrix4x4.CreateLookAt(Position, Direction, Up);
|
||||
public Matrix4x4 GetProjectionMatrix()
|
||||
=> Matrix4x4.CreatePerspectiveFieldOfView(Helper.DegreesToRadians(Zoom), AspectRatio, Near, Far);
|
||||
|
||||
private const float _step = 0.01f;
|
||||
private const float _zero = 0.000001f; // doesn't actually work if _infinite is used as max value /shrug
|
||||
private const float _infinite = 0.0f;
|
||||
private const ImGuiSliderFlags _clamp = ImGuiSliderFlags.AlwaysClamp;
|
||||
public void ImGuiCamera()
|
||||
{
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, new Vector2(8, 3));
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.CellPadding, new Vector2(0, 1));
|
||||
if (ImGui.BeginTable("camera_editor", 2))
|
||||
{
|
||||
SnimGui.Layout("Mode");
|
||||
ImGui.PushID(1);var m = (int) Mode;
|
||||
ImGui.Combo("world_mode", ref m, "Fly Cam\0Arcball\0");
|
||||
Mode = (WorldMode) m;ImGui.PopID();
|
||||
|
||||
SnimGui.Layout("Speed");ImGui.PushID(2);
|
||||
ImGui.DragFloat("", ref Speed, _step, _zero, _infinite, "%.2f m/s", _clamp);
|
||||
ImGui.PopID();SnimGui.Layout("Far Plane");ImGui.PushID(3);
|
||||
ImGui.DragFloat("", ref Far, 0.1f, 0.1f, Far * 2f, "%.2f m", _clamp);
|
||||
ImGui.PopID();
|
||||
|
||||
ImGui.EndTable();
|
||||
}
|
||||
ImGui.PopStyleVar(2);
|
||||
}
|
||||
}
|
||||
@@ -1,664 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CUE4Parse_Conversion;
|
||||
using CUE4Parse_Conversion.Options;
|
||||
using CUE4Parse.Utils;
|
||||
using FModel.Extensions;
|
||||
using FModel.Views.Snooper.Models;
|
||||
using ImGuiNET;
|
||||
using Serilog;
|
||||
using Serilog.Core;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace FModel.Views.Snooper;
|
||||
|
||||
public sealed class ExportModal
|
||||
{
|
||||
public static ExportModal Instance { get; } = new();
|
||||
|
||||
private const string Title = "Export Progress";
|
||||
private const string IconXMark = "\uf057";
|
||||
private const string IconFolder = "\uf08e";
|
||||
|
||||
private readonly Vector4[] _pieColors =
|
||||
[
|
||||
new(0.22f, 0.52f, 0.90f, 1f),
|
||||
new(0.28f, 0.78f, 0.44f, 1f),
|
||||
new(0.90f, 0.62f, 0.22f, 1f),
|
||||
new(0.75f, 0.32f, 0.75f, 1f),
|
||||
new(0.32f, 0.75f, 0.85f, 1f),
|
||||
new(0.85f, 0.32f, 0.45f, 1f),
|
||||
new(0.90f, 0.90f, 0.22f, 1f),
|
||||
new(0.55f, 0.75f, 0.32f, 1f),
|
||||
];
|
||||
|
||||
private static readonly Vector4 _redColor = new(1f, 0.4f, 0.4f, 1f);
|
||||
private static readonly Vector4 _orangeColor = new(1f, 0.5f, 0f, 1f);
|
||||
private static readonly Vector4 _yellowColor = new(1f, 1f, 0.4f, 1f);
|
||||
private static readonly Vector4 _greenColor = new(0.4f, 1f, 0.4f, 1f);
|
||||
|
||||
private bool _openPopup;
|
||||
private bool _modalOpen;
|
||||
private bool _inProgress;
|
||||
private CancellationTokenSource? _cts;
|
||||
private IReadOnlyList<ExportResult>? _exportResults;
|
||||
|
||||
private ExportProgress _currentProgress;
|
||||
private readonly IProgress<ExportProgress> _progress;
|
||||
private readonly Stopwatch _stopwatch = new();
|
||||
private readonly ConcurrentQueue<LogEvent> _pendingLogs = new();
|
||||
private readonly List<ClassGroup> _classGroups = [];
|
||||
|
||||
private const int MaxGraphSamples = 4096;
|
||||
private const float GraphSampleIntervalSec = 0.25f;
|
||||
private readonly List<float> _graphSamples = [];
|
||||
private float _graphNextSampleAt;
|
||||
private int _graphLastCompleted;
|
||||
|
||||
private ExportModal()
|
||||
{
|
||||
ImGuiSink.Instance.OnExporterLogEvent += _pendingLogs.Enqueue;
|
||||
_progress = new Progress<ExportProgress>(p => _currentProgress = p);
|
||||
}
|
||||
|
||||
public void Export(IEnumerable<IExportableThing> nodes, string exportDirectory, ExportOptions options)
|
||||
{
|
||||
Reset();
|
||||
_openPopup = true;
|
||||
_inProgress = true;
|
||||
_cts = new CancellationTokenSource();
|
||||
_stopwatch.Restart();
|
||||
|
||||
var token = _cts.Token;
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var session = new ExportSession();
|
||||
foreach (var node in nodes) node.AddToExportSession(session);
|
||||
_exportResults = await session.RunAsync(exportDirectory, options, _progress, token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Log.Error("Export cancelled by user");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Export failed");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_stopwatch.Stop();
|
||||
_inProgress = false;
|
||||
}
|
||||
}, token);
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
if (_openPopup)
|
||||
{
|
||||
ImGui.OpenPopup(Title);
|
||||
_modalOpen = true;
|
||||
_openPopup = false;
|
||||
}
|
||||
|
||||
if (!_modalOpen) return;
|
||||
|
||||
var viewport = ImGui.GetMainViewport();
|
||||
ImGui.SetNextWindowSize(viewport.WorkSize * 0.75f, ImGuiCond.Always);
|
||||
ImGui.SetNextWindowPos(viewport.GetCenter(), ImGuiCond.Always, new Vector2(0.5f, 0.5f));
|
||||
|
||||
var open = true;
|
||||
if (ImGui.BeginPopupModal(Title, ref open, ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoResize))
|
||||
{
|
||||
if (ImGui.BeginChild("##ModalInfoBody", Vector2.Zero, ImGuiChildFlags.FrameStyle))
|
||||
{
|
||||
DrawProgressBar();
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.SeparatorText("Throughput");
|
||||
DrawThroughputGraph();
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.SeparatorText("Export Log");
|
||||
DrawExportLog();
|
||||
}
|
||||
ImGui.EndChild();
|
||||
ImGui.EndPopup();
|
||||
}
|
||||
|
||||
if (!open)
|
||||
{
|
||||
_modalOpen = false;
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
_pendingLogs.Clear();
|
||||
_classGroups.Clear();
|
||||
_exportResults = null;
|
||||
_currentProgress = new ExportProgress(0, 0);
|
||||
_cts?.Cancel();
|
||||
_cts?.Dispose();
|
||||
_cts = null;
|
||||
|
||||
_graphSamples.Clear();
|
||||
_graphNextSampleAt = 0f;
|
||||
_graphLastCompleted = 0;
|
||||
}
|
||||
|
||||
private void DrawProgressBar()
|
||||
{
|
||||
var e = _stopwatch.Elapsed;
|
||||
ImGui.TextDisabled("\uf2f2");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted($"{e.Minutes:D2}:{e.Seconds:D2}.{e.Milliseconds / 10:D2}");
|
||||
|
||||
if (_inProgress && _currentProgress is { Total: > 0, Completed: > 1 })
|
||||
{
|
||||
var rate = _currentProgress.Completed / e.TotalSeconds;
|
||||
if (rate > 0)
|
||||
{
|
||||
var remaining = (_currentProgress.Total - _currentProgress.Completed) / rate;
|
||||
var eta = TimeSpan.FromSeconds(remaining);
|
||||
ImGui.SameLine();
|
||||
ImGui.TextDisabled("\uf017");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted($"ETA {eta.Minutes:D2}:{eta.Seconds:D2}");
|
||||
}
|
||||
}
|
||||
else if (!_inProgress && _exportResults is { Count: > 0 })
|
||||
{
|
||||
ImGui.SameLine();
|
||||
ImGui.TextColored(_greenColor, "\uf058");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted($"{_exportResults?.Count(r => r.Success) ?? 0} succeeded");
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.TextColored(_redColor, IconXMark);
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted($"{_exportResults?.Count(r => !r.Success) ?? 0} failed");
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
|
||||
var barColor = _classGroups.Any(cg => cg.ErrorCount > 0) ? new Vector4(0.75f, 0.32f, 0.32f, 1f) : _inProgress ? new Vector4(0.22f, 0.52f, 0.90f, 1f) : new Vector4(0.28f, 0.78f, 0.44f, 1f);
|
||||
var label = _inProgress && _currentProgress.Total > 0 ? _currentProgress.DisplayText : _inProgress ? "Preparing..." : "Done";
|
||||
|
||||
var barPos = ImGui.GetCursorScreenPos();
|
||||
var barSize = new Vector2(ImGui.GetContentRegionAvail().X, ImGui.GetFrameHeight());
|
||||
var hovered = ImGui.IsMouseHoveringRect(barPos, barPos + barSize);
|
||||
if (hovered && _inProgress)
|
||||
{
|
||||
barColor = new Vector4(0.75f, 0.32f, 0.32f, 1f);
|
||||
label = "\uf05e Cancel";
|
||||
}
|
||||
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 4f);
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.FrameBorderSize, 1f);
|
||||
ImGui.PushStyleColor(ImGuiCol.PlotHistogram, barColor);
|
||||
ImGui.ProgressBar(_currentProgress.Percentage, barSize, label);
|
||||
ImGui.PopStyleColor();
|
||||
ImGui.PopStyleVar(2);
|
||||
|
||||
if (_inProgress)
|
||||
{
|
||||
ImGui.SetCursorScreenPos(barPos);
|
||||
if (ImGui.InvisibleButton("##BarAction", barSize))
|
||||
{
|
||||
_cts?.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawThroughputGraph()
|
||||
{
|
||||
var elapsedSec = (float)_stopwatch.Elapsed.TotalSeconds;
|
||||
if (_inProgress && _currentProgress.Completed > 0 && elapsedSec >= _graphNextSampleAt && _graphSamples.Count < MaxGraphSamples)
|
||||
{
|
||||
var completed = _currentProgress.Completed;
|
||||
var rate = (completed - _graphLastCompleted) / GraphSampleIntervalSec;
|
||||
_graphSamples.Add(MathF.Max(rate, 0f));
|
||||
_graphLastCompleted = completed;
|
||||
_graphNextSampleAt = elapsedSec + GraphSampleIntervalSec;
|
||||
}
|
||||
|
||||
var graphPos = ImGui.GetCursorScreenPos();
|
||||
var graphW = ImGui.GetContentRegionAvail().X;
|
||||
var graphH = ImGui.GetFrameHeight() * 3f;
|
||||
var graphSize = new Vector2(graphW, graphH);
|
||||
ImGui.InvisibleButton("##ThroughputGraph", graphSize);
|
||||
var dl = ImGui.GetWindowDrawList();
|
||||
|
||||
dl.AddRectFilled(graphPos, graphPos + graphSize, 0xFF_14_14_14);
|
||||
|
||||
var count = _graphSamples.Count;
|
||||
switch (count)
|
||||
{
|
||||
case 0 when !_inProgress:
|
||||
{
|
||||
dl.AddRect(graphPos, graphPos + graphSize, 0xFF_2A_2A_2A);
|
||||
return;
|
||||
}
|
||||
case < 2:
|
||||
{
|
||||
if (_inProgress)
|
||||
{
|
||||
const string msg = "Collecting data...";
|
||||
var msgSz = ImGui.CalcTextSize(msg);
|
||||
dl.AddText(graphPos + (graphSize - msgSz) * 0.5f, 0x44_FF_FF_FF, msg);
|
||||
}
|
||||
dl.AddRect(graphPos, graphPos + graphSize, 0xFF_2A_2A_2A);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Y scale
|
||||
var maxVal = 0f;
|
||||
for (var i = 0; i < count; i++) maxVal = MathF.Max(maxVal, _graphSamples[i]);
|
||||
if (maxVal < 0.01f) maxVal = 1f;
|
||||
maxVal *= 1.15f; // top headroom
|
||||
|
||||
dl.PushClipRect(graphPos, graphPos + graphSize, true);
|
||||
|
||||
// Horizontal grid lines at 25 / 50 / 75 %
|
||||
for (var g = 1; g < 4; g++)
|
||||
{
|
||||
var gy = graphPos.Y + graphH * g / 4f;
|
||||
dl.AddLine(new Vector2(graphPos.X, gy), new Vector2(graphPos.X + graphW, gy), 0x18_FF_FF_FF, 0.5f);
|
||||
}
|
||||
|
||||
var lineColor = ImGui.GetColorU32(new Vector4(0.22f, 0.52f, 0.90f, 1f));
|
||||
var fillColor = ImGui.GetColorU32(new Vector4(0.22f, 0.52f, 0.90f, 0.15f));
|
||||
|
||||
// xStep shrinks as samples accumulate → graph zooms out naturally.
|
||||
// Oldest sample is always at x=0, newest at x=graphW.
|
||||
var xStep = graphW / (count - 1f);
|
||||
|
||||
// Filled area (series of convex quads)
|
||||
for (var i = 0; i < count - 1; i++)
|
||||
{
|
||||
var t0 = Math.Clamp(_graphSamples[i] / maxVal, 0f, 1f);
|
||||
var t1 = Math.Clamp(_graphSamples[i + 1] / maxVal, 0f, 1f);
|
||||
var x0 = graphPos.X + xStep * i;
|
||||
var x1 = graphPos.X + xStep * (i + 1);
|
||||
var y0 = graphPos.Y + graphH - graphH * t0;
|
||||
var y1 = graphPos.Y + graphH - graphH * t1;
|
||||
var bot = graphPos.Y + graphH;
|
||||
dl.AddQuadFilled(new Vector2(x0, y0), new Vector2(x1, y1), new Vector2(x1, bot), new Vector2(x0, bot), fillColor);
|
||||
}
|
||||
|
||||
// Line
|
||||
for (var i = 0; i < count - 1; i++)
|
||||
{
|
||||
var t0 = Math.Clamp(_graphSamples[i] / maxVal, 0f, 1f);
|
||||
var t1 = Math.Clamp(_graphSamples[i + 1] / maxVal, 0f, 1f);
|
||||
var x0 = graphPos.X + xStep * i;
|
||||
var x1 = graphPos.X + xStep * (i + 1);
|
||||
var y0 = graphPos.Y + graphH - graphH * t0;
|
||||
var y1 = graphPos.Y + graphH - graphH * t1;
|
||||
dl.AddLine(new Vector2(x0, y0), new Vector2(x1, y1), lineColor, 1.5f);
|
||||
}
|
||||
|
||||
// Dot on the newest sample (always at the right edge)
|
||||
var newestT = Math.Clamp(_graphSamples[count - 1] / maxVal, 0f, 1f);
|
||||
var newestY = graphPos.Y + graphH - graphH * newestT;
|
||||
dl.AddCircleFilled(new Vector2(graphPos.X + graphW, newestY), 3f, lineColor);
|
||||
|
||||
var rateStr = $"{_graphSamples[count - 1]:F1} items/s";
|
||||
dl.AddText(new Vector2(graphPos.X + 4, graphPos.Y + 3), 0xCC_FF_FF_FF, rateStr);
|
||||
|
||||
dl.PopClipRect();
|
||||
|
||||
dl.AddRect(graphPos, graphPos + graphSize, 0xFF_2A_2A_2A);
|
||||
}
|
||||
|
||||
private void DrawExportLog()
|
||||
{
|
||||
var avail = ImGui.GetContentRegionAvail();
|
||||
var rowH = ImGui.GetTextLineHeightWithSpacing();
|
||||
var canvasSize = 15 * rowH + ImGui.GetFrameHeightWithSpacing();
|
||||
var treeW = avail.X - canvasSize - ImGui.GetStyle().ItemSpacing.X;
|
||||
|
||||
DrainPendingLogs();
|
||||
|
||||
if (ImGui.BeginChild("##ExportLogTree", avail with { X = treeW }, ImGuiChildFlags.FrameStyle))
|
||||
{
|
||||
if (_classGroups.Count == 0)
|
||||
{
|
||||
ImGui.TextDisabled(_inProgress ? "Waiting for export data..." : "No export log.");
|
||||
}
|
||||
else for (var i = 0; i < _classGroups.Count; i++)
|
||||
{
|
||||
DrawClassGroup(i, _classGroups[i]);
|
||||
}
|
||||
}
|
||||
ImGui.EndChild();
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.BeginChild("##RightPanel", new Vector2(canvasSize, -1), ImGuiChildFlags.FrameStyle))
|
||||
{
|
||||
DrawPieCanvas();
|
||||
|
||||
if (_classGroups.Count == 0)
|
||||
{
|
||||
ImGui.TextDisabled(_inProgress ? "Waiting for data..." : "No export data.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var total = _classGroups.Sum(cg => cg.Objects.Count);
|
||||
for (var i = 0; i < _classGroups.Count; i++)
|
||||
{
|
||||
var cg = _classGroups[i];
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, _pieColors[i % _pieColors.Length]);
|
||||
ImGui.TextUnformatted("\uf111");
|
||||
ImGui.PopStyleColor();
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(cg.Name);
|
||||
ImGui.SameLine();
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled));
|
||||
ImGui.TextUnformatted($"({(float) cg.Objects.Count / total * 100f:F1}%)");
|
||||
ImGui.PopStyleColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.EndChild();
|
||||
}
|
||||
|
||||
private void DrawPieCanvas()
|
||||
{
|
||||
const int segments = 64;
|
||||
|
||||
var canvasPos = ImGui.GetCursorScreenPos();
|
||||
var size = ImGui.GetContentRegionAvail().X;
|
||||
var canvasVec = new Vector2(size);
|
||||
ImGui.InvisibleButton("##PieCanvas", canvasVec);
|
||||
var isHovered = ImGui.IsItemHovered();
|
||||
var mousePos = ImGui.GetMousePos();
|
||||
var dl = ImGui.GetWindowDrawList();
|
||||
|
||||
dl.AddRectFilled(canvasPos, canvasPos + canvasVec, 0xFF_14_14_14);
|
||||
dl.AddRect(canvasPos, canvasPos + canvasVec, 0xFF_32_32_32);
|
||||
|
||||
var total = _classGroups.Sum(cg => cg.Objects.Count);
|
||||
var padding = ImGui.GetFrameHeight() * 0.5f;
|
||||
var radius = size * 0.5f - padding;
|
||||
var center = canvasPos + new Vector2(size * 0.5f);
|
||||
|
||||
if (total == 0 || radius <= 0)
|
||||
{
|
||||
dl.AddCircleFilled(center, MathF.Max(radius, 1f), 0xFF_1F_1F_1F, segments);
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine hovered slice by angle
|
||||
var hoveredSlice = -1;
|
||||
if (isHovered)
|
||||
{
|
||||
var dx = mousePos.X - center.X;
|
||||
var dy = mousePos.Y - center.Y;
|
||||
if (dx * dx + dy * dy <= radius * radius)
|
||||
{
|
||||
var angle = MathF.Atan2(dy, dx);
|
||||
while (angle < -MathF.PI / 2f) angle += MathF.PI * 2f;
|
||||
var cur = -MathF.PI / 2f;
|
||||
for (var i = 0; i < _classGroups.Count; i++)
|
||||
{
|
||||
var sweep = (float)_classGroups[i].Objects.Count / total * MathF.PI * 2f;
|
||||
if (angle >= cur && angle < cur + sweep) { hoveredSlice = i; break; }
|
||||
cur += sweep;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float startAngle = -MathF.PI / 2f;
|
||||
for (var i = 0; i < _classGroups.Count; i++)
|
||||
{
|
||||
var cg = _classGroups[i];
|
||||
var ratio = (float) cg.Objects.Count / total;
|
||||
var sliceAngle = ratio * MathF.PI * 2f;
|
||||
var col = ImGui.GetColorU32(_pieColors[i % _pieColors.Length]);
|
||||
var r = i == hoveredSlice ? radius + padding * 0.25f : radius;
|
||||
dl.PathLineTo(center);
|
||||
dl.PathArcTo(center, r, startAngle, startAngle + sliceAngle);
|
||||
dl.PathFillConvex(col);
|
||||
|
||||
var midAngle = startAngle + sliceAngle * 0.5f;
|
||||
var labelPos = center + new Vector2(MathF.Cos(midAngle), MathF.Sin(midAngle)) * (radius * 0.62f);
|
||||
var pctStr = $"{ratio * 100f:F0}%";
|
||||
dl.AddText(labelPos - ImGui.CalcTextSize(pctStr) * 0.5f, 0xFF_FF_FF_FF, pctStr);
|
||||
|
||||
startAngle += sliceAngle;
|
||||
}
|
||||
|
||||
dl.AddCircle(center, radius, 0xAA_00_00_00, segments, 1.5f);
|
||||
|
||||
if (hoveredSlice >= 0)
|
||||
{
|
||||
ImGui.BeginTooltip();
|
||||
|
||||
var cg = _classGroups[hoveredSlice];
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, _pieColors[hoveredSlice % _pieColors.Length]);
|
||||
ImGui.TextUnformatted("\uf111");
|
||||
ImGui.PopStyleColor();
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(cg.Name);
|
||||
|
||||
ImGui.EndTooltip();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawClassGroup(int index, ClassGroup cg)
|
||||
{
|
||||
ImGui.PushStyleColor(ImGuiCol.Header, new Vector4(0.20f, 0.20f, 0.20f, 1.00f));
|
||||
ImGui.PushStyleColor(ImGuiCol.HeaderHovered, new Vector4(0.69f, 0.69f, 1.00f, 0.20f));
|
||||
ImGui.PushStyleColor(ImGuiCol.HeaderActive, new Vector4(0.69f, 0.69f, 1.00f, 0.20f));
|
||||
var open = ImGui.CollapsingHeader($"{cg.Name} ({cg.Objects.Count})##class_{cg.Name}");
|
||||
ImGui.PopStyleColor(3);
|
||||
var headerMin = ImGui.GetItemRectMin();
|
||||
var headerMax = ImGui.GetItemRectMax();
|
||||
|
||||
var labelW = MathF.Floor(ImGui.GetStyle().ItemSpacing.X * 0.5f);
|
||||
var col = ImGui.GetColorU32(_pieColors[index % _pieColors.Length]);
|
||||
ImGui.GetWindowDrawList().AddRectFilled(headerMin, headerMax with { X = headerMin.X + labelW }, col);
|
||||
|
||||
if (cg.ErrorCount > 0 && ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.SetTooltip($"{cg.ErrorCount} error{(cg.ErrorCount > 1 ? "s" : "")} in this class");
|
||||
}
|
||||
|
||||
if (!open) return;
|
||||
foreach (var og in cg.Objects)
|
||||
{
|
||||
DrawObjectGroup(cg.Name, og);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawObjectGroup(string className, ObjectGroup og)
|
||||
{
|
||||
var rightEdge = ImGui.GetCursorPosX() + ImGui.GetContentRegionAvail().X;
|
||||
|
||||
var hasErr = og.ErrorCount > 0;
|
||||
if (hasErr) ImGui.PushStyleColor(ImGuiCol.Text, _redColor);
|
||||
var flags = ImGuiTreeNodeFlags.AllowOverlap | ImGuiTreeNodeFlags.SpanFullWidth | ImGuiTreeNodeFlags.FramePadding;
|
||||
var open = ImGui.TreeNodeEx($"{og.Name}##obj_{className}_{og.Name}", flags);
|
||||
if (hasErr) ImGui.PopStyleColor();
|
||||
|
||||
if (og.Entries.FirstOrDefault(e => !string.IsNullOrEmpty(e.FilePath)) is { } first)
|
||||
{
|
||||
var style = ImGui.GetStyle();
|
||||
var btnW = ImGui.CalcTextSize(IconFolder).X + style.FramePadding.X * 2;
|
||||
ImGui.SameLine(rightEdge - btnW);
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, style.ItemSpacing with { X = 0 });
|
||||
ImGui.PushStyleColor(ImGuiCol.Button, Vector4.Zero);
|
||||
if (ImGui.Button($"{IconFolder}##obj_{className}_{og.Name}"))
|
||||
{
|
||||
OpenInExplorer(first.FilePath!);
|
||||
}
|
||||
if (ImGui.IsItemHovered()) ImGui.SetTooltip("Open In Explorer");
|
||||
ImGui.PopStyleColor();
|
||||
ImGui.PopStyleVar();
|
||||
}
|
||||
|
||||
if (!open) return;
|
||||
foreach (var entry in og.Entries)
|
||||
{
|
||||
DrawLogEntry(entry);
|
||||
}
|
||||
ImGui.TreePop();
|
||||
}
|
||||
|
||||
private void DrawLogEntry(LogEntry entry)
|
||||
{
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, entry.Color);
|
||||
ImGui.TextUnformatted(entry.Icon);
|
||||
ImGui.PopStyleColor();
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(entry.Message);
|
||||
if (ImGui.IsItemHovered() && entry.Exception != null)
|
||||
{
|
||||
DrawExceptionTooltip(entry.Exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawExceptionTooltip(Exception ex)
|
||||
{
|
||||
ImGui.BeginTooltip();
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, _redColor);
|
||||
ImGui.TextUnformatted(ex.GetType().ToString());
|
||||
ImGui.PopStyleColor();
|
||||
ImGui.SameLine(0, 0);
|
||||
ImGui.TextDisabled(":");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(ex.Message);
|
||||
if (!string.IsNullOrEmpty(ex.StackTrace))
|
||||
{
|
||||
ImGui.SetWindowFontScale(0.85f);
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled));
|
||||
ImGui.TextUnformatted(ex.StackTrace);
|
||||
ImGui.PopStyleColor();
|
||||
ImGui.SetWindowFontScale(1.0f);
|
||||
}
|
||||
ImGui.EndTooltip();
|
||||
}
|
||||
|
||||
private void OpenInExplorer(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(path) || Directory.Exists(path)) Process.Start("explorer.exe", $"/select, \"{path}\"");
|
||||
else Log.Warning("File or directory does not exist: {Path}", path);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failed to open in explorer: {Path}", path);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrainPendingLogs()
|
||||
{
|
||||
while (_pendingLogs.TryDequeue(out var log))
|
||||
{
|
||||
var className = log.GetContext("ClassName");
|
||||
var objectPath = log.GetContext("ObjectPath");
|
||||
var filePath = log.GetContext("FilePath");
|
||||
|
||||
var cg = FindOrCreateClass(className);
|
||||
var og = FindOrCreateObject(cg, objectPath);
|
||||
var entry = new LogEntry(log, filePath);
|
||||
if (entry.Icon == IconXMark)
|
||||
{
|
||||
og.ErrorCount++;
|
||||
cg.ErrorCount++;
|
||||
}
|
||||
og.Entries.Add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
private ClassGroup FindOrCreateClass(string name)
|
||||
{
|
||||
foreach (var cg in _classGroups)
|
||||
if (cg.Name == name) return cg;
|
||||
|
||||
var n = new ClassGroup(name);
|
||||
_classGroups.Add(n);
|
||||
return n;
|
||||
}
|
||||
|
||||
private static ObjectGroup FindOrCreateObject(ClassGroup cg, string path)
|
||||
{
|
||||
foreach (var og in cg.Objects)
|
||||
if (og.Path == path) return og;
|
||||
|
||||
var n = new ObjectGroup(path);
|
||||
cg.Objects.Add(n);
|
||||
return n;
|
||||
}
|
||||
|
||||
private sealed class LogEntry(LogEvent log, string? filePath)
|
||||
{
|
||||
public string Icon { get; } = log.Level switch
|
||||
{
|
||||
LogEventLevel.Error or LogEventLevel.Fatal => IconXMark,
|
||||
LogEventLevel.Warning => "\uf071",
|
||||
LogEventLevel.Information => "\uf05a",
|
||||
LogEventLevel.Debug => "\uf188",
|
||||
_ => "\uf5dc"
|
||||
};
|
||||
public Vector4 Color { get; } = log.Level switch
|
||||
{
|
||||
LogEventLevel.Error or LogEventLevel.Fatal => _redColor,
|
||||
LogEventLevel.Warning => _yellowColor,
|
||||
_ => new Vector4(0.5f, 0.5f, 0.5f, 1f)
|
||||
};
|
||||
public string Message { get; } = $"[{log.Timestamp:HH:mm:ss.fff}] {log.RenderMessage()}";
|
||||
public string? FilePath { get; } = filePath;
|
||||
public Exception? Exception { get; } = log.Exception;
|
||||
}
|
||||
|
||||
private sealed class ObjectGroup(string path)
|
||||
{
|
||||
public string Path { get; } = path;
|
||||
public string Name { get; } = path.SubstringAfterLast('.');
|
||||
public List<LogEntry> Entries { get; } = [];
|
||||
public int ErrorCount { get; set; }
|
||||
}
|
||||
|
||||
private sealed class ClassGroup(string name)
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
public List<ObjectGroup> Objects { get; } = [];
|
||||
public int ErrorCount { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
public class ImGuiSink : ILogEventSink
|
||||
{
|
||||
public static ImGuiSink Instance { get; } = new();
|
||||
|
||||
private ImGuiSink()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public event Action<LogEvent>? OnExporterLogEvent;
|
||||
|
||||
public void Emit(LogEvent logEvent)
|
||||
{
|
||||
if (logEvent.Properties.TryGetValue("ExporterV2", out var state) && state is ScalarValue { Value: true })
|
||||
{
|
||||
OnExporterLogEvent?.Invoke(logEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using FModel.Views.Snooper.Buffers;
|
||||
using FModel.Views.Snooper.Shading;
|
||||
using ImGuiNET;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
|
||||
namespace FModel.Views.Snooper.Lights;
|
||||
|
||||
public abstract class Light : IDisposable
|
||||
{
|
||||
private int _handle;
|
||||
|
||||
private BufferObject<uint> _ebo;
|
||||
private BufferObject<float> _vbo;
|
||||
private BufferObject<Matrix4x4> _matrixVbo;
|
||||
private VertexArrayObject<float, uint> _vao;
|
||||
|
||||
public readonly uint[] Indices = { 0, 1, 2, 3, 4, 5 };
|
||||
public readonly float[] Vertices = {
|
||||
1f, 1f, 0f,
|
||||
-1f, -1f, 0f,
|
||||
-1f, 1f, 0f,
|
||||
-1f, -1f, 0f,
|
||||
1f, 1f, 0f,
|
||||
1f, -1f, 0
|
||||
};
|
||||
public readonly FGuid Model;
|
||||
public readonly Texture Icon;
|
||||
public Transform Transform;
|
||||
|
||||
public Vector4 Color;
|
||||
public float Intensity;
|
||||
public bool IsSetup;
|
||||
|
||||
public Light(Texture icon, UObject light)
|
||||
{
|
||||
Transform = new Transform
|
||||
{
|
||||
Position = light.GetOrDefault("RelativeLocation", FVector.ZeroVector) * Constants.SCALE_DOWN_RATIO,
|
||||
Rotation = light.GetOrDefault("RelativeRotation", FRotator.ZeroRotator).Quaternion(),
|
||||
Scale = light.GetOrDefault("RelativeScale3D", FVector.OneVector)
|
||||
};
|
||||
|
||||
Model = new FGuid((uint) light.GetFullName().GetHashCode());
|
||||
Icon = icon;
|
||||
|
||||
Color = light.GetOrDefault("LightColor", new FColor(0xFF, 0xFF, 0xFF, 0xFF));
|
||||
Intensity = light.GetOrDefault("Intensity", 1.0f);
|
||||
}
|
||||
|
||||
public Light(FGuid model, Texture icon, UObject parent, UObject light, Transform transform)
|
||||
{
|
||||
Transform = new Transform
|
||||
{
|
||||
Relation = transform.Matrix,
|
||||
Position = light.GetOrDefault("RelativeLocation", parent.GetOrDefault("RelativeLocation", FVector.ZeroVector)) * Constants.SCALE_DOWN_RATIO,
|
||||
Rotation = light.GetOrDefault("RelativeRotation", parent.GetOrDefault("RelativeRotation", FRotator.ZeroRotator)).Quaternion(),
|
||||
Scale = light.GetOrDefault("RelativeScale3D", parent.GetOrDefault("RelativeScale3D", FVector.OneVector))
|
||||
};
|
||||
|
||||
Model = model;
|
||||
Icon = icon;
|
||||
|
||||
Color = light.GetOrDefault("LightColor", parent.GetOrDefault("LightColor", new FColor(0xFF, 0xFF, 0xFF, 0xFF)));
|
||||
Intensity = light.GetOrDefault("Intensity", parent.GetOrDefault("Intensity", 1.0f));
|
||||
}
|
||||
|
||||
public void SetupInstances()
|
||||
{
|
||||
var instanceMatrix = new [] {Transform.Matrix};
|
||||
_matrixVbo = new BufferObject<Matrix4x4>(instanceMatrix, BufferTarget.ArrayBuffer);
|
||||
_vao.BindInstancing(); // VertexAttributePointer
|
||||
}
|
||||
|
||||
public void UpdateMatrices()
|
||||
{
|
||||
_matrixVbo.Bind();
|
||||
_matrixVbo.Update(0, Transform.Matrix);
|
||||
_matrixVbo.Unbind();
|
||||
}
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
_handle = GL.CreateProgram();
|
||||
|
||||
_ebo = new BufferObject<uint>(Indices, BufferTarget.ElementArrayBuffer);
|
||||
_vbo = new BufferObject<float>(Vertices, BufferTarget.ArrayBuffer);
|
||||
_vao = new VertexArrayObject<float, uint>(_vbo, _ebo);
|
||||
|
||||
_vao.VertexAttributePointer(0, 3, VertexAttribPointerType.Float, 3, 0); // position
|
||||
SetupInstances();
|
||||
|
||||
IsSetup = true;
|
||||
}
|
||||
|
||||
public void Render(Shader shader)
|
||||
{
|
||||
GL.Disable(EnableCap.CullFace);
|
||||
|
||||
_vao.Bind();
|
||||
|
||||
Icon?.Bind(TextureUnit.Texture0);
|
||||
shader.SetUniform("uIcon", 0);
|
||||
shader.SetUniform("uColor", Color);
|
||||
|
||||
GL.DrawArrays(PrimitiveType.Triangles, 0, Indices.Length);
|
||||
|
||||
GL.Enable(EnableCap.CullFace);
|
||||
}
|
||||
|
||||
public virtual void Render(int i, Shader shader)
|
||||
{
|
||||
shader.SetUniform($"uLights[{i}].Base.Color", Color);
|
||||
shader.SetUniform($"uLights[{i}].Base.Position", Transform.Matrix.Translation);
|
||||
shader.SetUniform($"uLights[{i}].Base.Intensity", Intensity);
|
||||
}
|
||||
|
||||
public virtual void ImGuiLight()
|
||||
{
|
||||
SnimGui.Layout("Color");ImGui.PushID(1);
|
||||
ImGui.ColorEdit4("", ref Color, ImGuiColorEditFlags.NoAlpha);
|
||||
ImGui.PopID();SnimGui.Layout("Intensity");ImGui.PushID(2);
|
||||
ImGui.DragFloat("", ref Intensity, 0.1f);ImGui.PopID();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_ebo?.Dispose();
|
||||
_vbo?.Dispose();
|
||||
_vao?.Dispose();
|
||||
GL.DeleteProgram(_handle);
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
using System;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using FModel.Views.Snooper.Shading;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace FModel.Views.Snooper.Lights;
|
||||
|
||||
public class PointLight : Light
|
||||
{
|
||||
public float Linear;
|
||||
public float Quadratic;
|
||||
|
||||
public PointLight(Texture icon, UObject point) : base(icon, point)
|
||||
{
|
||||
if (!point.TryGetValue(out float radius, "SourceRadius", "AttenuationRadius"))
|
||||
radius = 1.0f;
|
||||
|
||||
radius *= Constants.SCALE_DOWN_RATIO;
|
||||
Linear = 4.5f / radius;
|
||||
Quadratic = 75.0f / MathF.Pow(radius, 2.0f);
|
||||
}
|
||||
|
||||
public PointLight(FGuid model, Texture icon, UObject parent, UObject point, Transform transform) : base(model, icon, parent, point, transform)
|
||||
{
|
||||
if (!point.TryGetValue(out float radius, "AttenuationRadius", "SourceRadius"))
|
||||
radius = 1.0f;
|
||||
|
||||
radius *= Constants.SCALE_DOWN_RATIO;
|
||||
Linear = 4.5f / radius;
|
||||
Quadratic = 75.0f / MathF.Pow(radius, 2.0f);
|
||||
}
|
||||
|
||||
public override void Render(int i, Shader shader)
|
||||
{
|
||||
base.Render(i, shader);
|
||||
shader.SetUniform($"uLights[{i}].Linear", Linear);
|
||||
shader.SetUniform($"uLights[{i}].Quadratic", Quadratic);
|
||||
|
||||
shader.SetUniform($"uLights[{i}].Type", 0);
|
||||
}
|
||||
|
||||
public override void ImGuiLight()
|
||||
{
|
||||
base.ImGuiLight();
|
||||
SnimGui.Layout("Linear");ImGui.PushID(3);
|
||||
ImGui.DragFloat("", ref Linear, 0.1f);
|
||||
ImGui.PopID();SnimGui.Layout("Quadratic");ImGui.PushID(4);
|
||||
ImGui.DragFloat("", ref Quadratic, 0.1f);ImGui.PopID();
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using FModel.Views.Snooper.Shading;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace FModel.Views.Snooper.Lights;
|
||||
|
||||
public class SpotLight : Light
|
||||
{
|
||||
public float Attenuation;
|
||||
public float InnerConeAngle;
|
||||
public float OuterConeAngle;
|
||||
|
||||
public SpotLight(Texture icon, UObject spot) : base(icon, spot)
|
||||
{
|
||||
if (!spot.TryGetValue(out Attenuation, "SourceRadius", "AttenuationRadius"))
|
||||
Attenuation = 1.0f;
|
||||
|
||||
Attenuation *= Constants.SCALE_DOWN_RATIO;
|
||||
InnerConeAngle = spot.GetOrDefault("InnerConeAngle", 50.0f);
|
||||
OuterConeAngle = spot.GetOrDefault("OuterConeAngle", InnerConeAngle + 10);
|
||||
if (OuterConeAngle < InnerConeAngle)
|
||||
InnerConeAngle = OuterConeAngle - 10;
|
||||
}
|
||||
|
||||
public SpotLight(FGuid model, Texture icon, UObject parent, UObject spot, Transform transform) : base(model, icon, parent, spot, transform)
|
||||
{
|
||||
if (!spot.TryGetValue(out Attenuation, "AttenuationRadius", "SourceRadius"))
|
||||
Attenuation = 1.0f;
|
||||
|
||||
Attenuation *= Constants.SCALE_DOWN_RATIO;
|
||||
InnerConeAngle = spot.GetOrDefault("InnerConeAngle", 50.0f);
|
||||
OuterConeAngle = spot.GetOrDefault("OuterConeAngle", InnerConeAngle + 10);
|
||||
if (OuterConeAngle < InnerConeAngle)
|
||||
InnerConeAngle = OuterConeAngle - 10;
|
||||
}
|
||||
|
||||
public override void Render(int i, Shader shader)
|
||||
{
|
||||
base.Render(i, shader);
|
||||
shader.SetUniform($"uLights[{i}].Attenuation", Attenuation);
|
||||
shader.SetUniform($"uLights[{i}].InnerConeAngle", InnerConeAngle);
|
||||
shader.SetUniform($"uLights[{i}].OuterConeAngle", OuterConeAngle);
|
||||
|
||||
shader.SetUniform($"uLights[{i}].Type", 1);
|
||||
}
|
||||
|
||||
public override void ImGuiLight()
|
||||
{
|
||||
base.ImGuiLight();
|
||||
SnimGui.Layout("Attenuation");ImGui.PushID(3);
|
||||
ImGui.DragFloat("", ref Attenuation, 0.1f);ImGui.PopID();
|
||||
SnimGui.Layout("Inner Cone Angle");ImGui.PushID(4);
|
||||
ImGui.DragFloat("", ref InnerConeAngle, 0.1f, 0.0f, 90.0f, "%.1f°");ImGui.PopID();
|
||||
SnimGui.Layout("Outer Cone Angle");ImGui.PushID(5);
|
||||
ImGui.DragFloat("", ref OuterConeAngle, 0.1f, 0.0f, 90.0f, "%.1f°");ImGui.PopID();
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
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(IRenderableModel 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(IRenderableModel attachedTo, Transform transform, Socket socket, SocketAttachementInfo info)
|
||||
{
|
||||
socket.AttachedModels.Remove(info);
|
||||
SafeDetach(attachedTo, transform);
|
||||
}
|
||||
|
||||
public void SafeDetach(IRenderableModel 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}'");
|
||||
}
|
||||
@@ -1,228 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using CUE4Parse.UE4.Objects.PhysicsEngine;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using FModel.Views.Snooper.Buffers;
|
||||
using FModel.Views.Snooper.Shading;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
|
||||
namespace FModel.Views.Snooper.Models;
|
||||
|
||||
public class Collision : IDisposable
|
||||
{
|
||||
private const int Slices = 16;
|
||||
private const int Stacks = 8;
|
||||
private const float SectorStep = 2 * MathF.PI / Slices;
|
||||
private const float StackStep = MathF.PI / Stacks;
|
||||
|
||||
private readonly int[] _indexData;
|
||||
private readonly FVector[] _vertexData;
|
||||
private readonly Transform _transform;
|
||||
public readonly string LoweredBoneName;
|
||||
|
||||
private int _handle;
|
||||
private BufferObject<int> _ebo { get; set; }
|
||||
private BufferObject<FVector> _vbo { get; set; }
|
||||
private VertexArrayObject<FVector, int> _vao { get; set; }
|
||||
|
||||
private Collision()
|
||||
{
|
||||
_indexData = [];
|
||||
_vertexData = [];
|
||||
_transform = Transform.Identity;
|
||||
}
|
||||
|
||||
private Collision(FName boneName) : this()
|
||||
{
|
||||
LoweredBoneName = boneName.Text.ToLower();
|
||||
}
|
||||
|
||||
public Collision(FKConvexElem convexElems, FName boneName = default) : this(boneName)
|
||||
{
|
||||
_indexData = convexElems.IndexData;
|
||||
_vertexData = convexElems.VertexData;
|
||||
_transform = new Transform
|
||||
{
|
||||
Position = convexElems.Transform.Translation * Constants.SCALE_DOWN_RATIO,
|
||||
Rotation = convexElems.Transform.Rotation,
|
||||
Scale = convexElems.Transform.Scale3D
|
||||
};
|
||||
}
|
||||
|
||||
public Collision(FKSphereElem sphereElem, FName boneName = default) : this(boneName)
|
||||
{
|
||||
_vertexData = new FVector[(Slices + 1) * (Stacks + 1)];
|
||||
for (var i = 0; i <= Stacks; i++)
|
||||
{
|
||||
var stackAngle = MathF.PI / 2 - i * StackStep;
|
||||
var xy = MathF.Cos(stackAngle);
|
||||
var z = MathF.Sin(stackAngle);
|
||||
|
||||
for (var j = 0; j <= Slices; j++)
|
||||
{
|
||||
var sectorAngle = j * SectorStep;
|
||||
var x = xy * MathF.Cos(sectorAngle);
|
||||
var y = xy * MathF.Sin(sectorAngle);
|
||||
_vertexData[i * (Slices + 1) + j] = new FVector(x, y, z);
|
||||
}
|
||||
}
|
||||
|
||||
_indexData = new int[Stacks * Slices * 6];
|
||||
for (var i = 0; i < Stacks; i++)
|
||||
{
|
||||
for (var j = 0; j < Slices; j++)
|
||||
{
|
||||
var a = i * (Slices + 1) + j;
|
||||
var b = a + Slices + 1;
|
||||
_indexData[(i * Slices + j) * 6 + 0] = a;
|
||||
_indexData[(i * Slices + j) * 6 + 1] = b;
|
||||
_indexData[(i * Slices + j) * 6 + 2] = a + 1;
|
||||
_indexData[(i * Slices + j) * 6 + 3] = b;
|
||||
_indexData[(i * Slices + j) * 6 + 4] = b + 1;
|
||||
_indexData[(i * Slices + j) * 6 + 5] = a + 1;
|
||||
}
|
||||
}
|
||||
|
||||
_transform = new Transform
|
||||
{
|
||||
Position = sphereElem.Center * Constants.SCALE_DOWN_RATIO,
|
||||
Scale = new FVector(sphereElem.Radius)
|
||||
};
|
||||
}
|
||||
|
||||
public Collision(FKBoxElem boxElem, FName boneName = default) : this(boneName)
|
||||
{
|
||||
_vertexData =
|
||||
[
|
||||
new FVector(-boxElem.X, -boxElem.Y, -boxElem.Z),
|
||||
new FVector(boxElem.X, -boxElem.Y, -boxElem.Z),
|
||||
new FVector(boxElem.X, boxElem.Y, -boxElem.Z),
|
||||
new FVector(-boxElem.X, boxElem.Y, -boxElem.Z),
|
||||
new FVector(-boxElem.X, -boxElem.Y, boxElem.Z),
|
||||
new FVector(boxElem.X, -boxElem.Y, boxElem.Z),
|
||||
new FVector(boxElem.X, boxElem.Y, boxElem.Z),
|
||||
new FVector(-boxElem.X, boxElem.Y, boxElem.Z)
|
||||
];
|
||||
|
||||
_indexData =
|
||||
[
|
||||
0, 1, 2, 2, 3, 0,
|
||||
1, 5, 6, 6, 2, 1,
|
||||
5, 4, 7, 7, 6, 5,
|
||||
4, 0, 3, 3, 7, 4,
|
||||
3, 2, 6, 6, 7, 3,
|
||||
4, 5, 1, 1, 0, 4
|
||||
];
|
||||
|
||||
_transform = new Transform
|
||||
{
|
||||
Position = boxElem.Center * Constants.SCALE_DOWN_RATIO,
|
||||
Rotation = boxElem.Rotation.Quaternion(),
|
||||
Scale = new FVector(.5f)
|
||||
};
|
||||
}
|
||||
|
||||
public Collision(FKSphylElem sphylElem, FName boneName = default)
|
||||
: this(sphylElem.Length, [sphylElem.Radius, sphylElem.Radius], sphylElem.Center, sphylElem.Rotation, boneName) {}
|
||||
public Collision(FKTaperedCapsuleElem taperedCapsuleElem, FName boneName = default)
|
||||
: this(taperedCapsuleElem.Length, [taperedCapsuleElem.Radius1, taperedCapsuleElem.Radius0], taperedCapsuleElem.Center, taperedCapsuleElem.Rotation, boneName) {}
|
||||
|
||||
private Collision(float length, float[] radius, FVector center = default, FRotator rotator = default, FName boneName = default) : this(boneName)
|
||||
{
|
||||
int vLength = 0;
|
||||
int half = Slices / 2;
|
||||
int k2 = (Slices + 1) * (Stacks + 1);
|
||||
|
||||
_vertexData = new FVector[k2 + Slices + 1];
|
||||
for(int i = 0; i < 2; ++i)
|
||||
{
|
||||
float h = -length / 2.0f + i * length;
|
||||
int start = i == 0 ? Stacks / 2 : 0;
|
||||
int end = i == 0 ? Stacks : Stacks / 2;
|
||||
|
||||
for(int j = start; j <= end; ++j)
|
||||
{
|
||||
var stackAngle = MathF.PI / 2 - j * StackStep;
|
||||
var xy = radius[i] * MathF.Cos(stackAngle);
|
||||
var z = radius[i] * MathF.Sin(stackAngle) + h;
|
||||
|
||||
for(int k = 0; k <= Slices; ++k)
|
||||
{
|
||||
var sectorAngle = k * SectorStep;
|
||||
var x = xy * MathF.Cos(sectorAngle);
|
||||
var y = xy * MathF.Sin(sectorAngle);
|
||||
_vertexData[vLength++] = new FVector(x, y, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var indices = new List<int>();
|
||||
AddIndicesForSlices(indices, ref k2);
|
||||
indices.AddRange(new[] {0, k2, k2, k2 - half, half, half});
|
||||
AddIndicesForStacks(indices);
|
||||
half /= 2;
|
||||
indices.AddRange(new[] {half, k2 - half * 3, k2 - half * 3, half * 3, k2 - half, k2 - half});
|
||||
AddIndicesForStacks(indices, Stacks / 2);
|
||||
_indexData = indices.ToArray();
|
||||
|
||||
_transform = new Transform
|
||||
{
|
||||
Position = center * Constants.SCALE_DOWN_RATIO,
|
||||
Rotation = rotator.Quaternion()
|
||||
};
|
||||
}
|
||||
|
||||
private void AddIndicesForSlices(List<int> indices, ref int k2)
|
||||
{
|
||||
for(int k1 = 0; k1 < Slices; ++k1, ++k2)
|
||||
{
|
||||
indices.AddRange(new[] {k1, k1 + 1, k1 + 1, k2, k2 + 1, k2 + 1});
|
||||
}
|
||||
}
|
||||
|
||||
private void AddIndicesForStacks(List<int> indices, int start = 0)
|
||||
{
|
||||
for (int k1 = start; k1 < Stacks * Slices + Slices; k1 += Slices + 1)
|
||||
{
|
||||
if (k1 == Stacks / 2 * (Slices + 1) + start) continue;
|
||||
indices.AddRange(new[] {k1, k1 + Slices + 1, k1 + Slices + 1, k1 + Slices / 2, k1 + Slices / 2 + Slices + 1, k1 + Slices / 2 + Slices + 1});
|
||||
}
|
||||
}
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
_handle = GL.CreateProgram();
|
||||
_ebo = new BufferObject<int>(_indexData, BufferTarget.ElementArrayBuffer);
|
||||
_vbo = new BufferObject<FVector>(_vertexData, BufferTarget.ArrayBuffer);
|
||||
_vao = new VertexArrayObject<FVector, int>(_vbo, _ebo);
|
||||
|
||||
_vao.VertexAttributePointer(0, 3, VertexAttribPointerType.Float, 1, 0);
|
||||
_vao.Unbind();
|
||||
}
|
||||
|
||||
public void Render(Shader shader, Matrix4x4 boneMatrix)
|
||||
{
|
||||
shader.SetUniform("uCollisionMatrix", _transform.Matrix * boneMatrix);
|
||||
|
||||
_vao.Bind();
|
||||
if (_indexData.Length > 0)
|
||||
{
|
||||
GL.DrawElements(PrimitiveType.Triangles, _ebo.Size, DrawElementsType.UnsignedInt, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
GL.DrawArrays(PrimitiveType.Points, 0, _vbo.Size);
|
||||
}
|
||||
_vao.Unbind();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_ebo?.Dispose();
|
||||
_vbo?.Dispose();
|
||||
_vao?.Dispose();
|
||||
GL.DeleteProgram(_handle);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
namespace FModel.Views.Snooper.Models;
|
||||
|
||||
public enum EAttribute
|
||||
{
|
||||
Index,
|
||||
Position,
|
||||
Normals,
|
||||
Tangent,
|
||||
UVs,
|
||||
Layer,
|
||||
Colors,
|
||||
BonesId,
|
||||
BonesWeight
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using FModel.Views.Snooper.Buffers;
|
||||
using FModel.Views.Snooper.Shading;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
|
||||
namespace FModel.Views.Snooper.Models;
|
||||
|
||||
public class Grid : IDisposable
|
||||
{
|
||||
private int _handle;
|
||||
|
||||
private BufferObject<uint> _ebo;
|
||||
private BufferObject<float> _vbo;
|
||||
private VertexArrayObject<float, uint> _vao;
|
||||
|
||||
private Shader _shader;
|
||||
|
||||
public readonly uint[] Indices = { 0, 1, 2, 3, 4, 5 };
|
||||
public readonly float[] Vertices = {
|
||||
1f, 1f, 0f,
|
||||
-1f, -1f, 0f,
|
||||
-1f, 1f, 0f,
|
||||
-1f, -1f, 0f,
|
||||
1f, 1f, 0f,
|
||||
1f, -1f, 0
|
||||
};
|
||||
|
||||
public Grid() {}
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
_handle = GL.CreateProgram();
|
||||
|
||||
_ebo = new BufferObject<uint>(Indices, BufferTarget.ElementArrayBuffer);
|
||||
_vbo = new BufferObject<float>(Vertices, BufferTarget.ArrayBuffer);
|
||||
_vao = new VertexArrayObject<float, uint>(_vbo, _ebo);
|
||||
|
||||
_shader = new Shader("grid");
|
||||
|
||||
_vao.VertexAttributePointer(0, 3, VertexAttribPointerType.Float, 3, 0); // position
|
||||
}
|
||||
|
||||
public void Render(Matrix4x4 viewMatrix, Matrix4x4 projMatrix, float near, float far)
|
||||
{
|
||||
GL.Disable(EnableCap.CullFace);
|
||||
GL.Disable(EnableCap.DepthTest);
|
||||
|
||||
_vao.Bind();
|
||||
|
||||
_shader.Use();
|
||||
_shader.SetUniform("view", viewMatrix);
|
||||
_shader.SetUniform("proj", projMatrix);
|
||||
_shader.SetUniform("uNear", near);
|
||||
_shader.SetUniform("uFar", far);
|
||||
|
||||
GL.DrawArrays(PrimitiveType.Triangles, 0, Indices.Length);
|
||||
|
||||
GL.Enable(EnableCap.DepthTest);
|
||||
GL.Enable(EnableCap.CullFace);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_ebo?.Dispose();
|
||||
_vbo?.Dispose();
|
||||
_vao?.Dispose();
|
||||
_shader?.Dispose();
|
||||
GL.DeleteProgram(_handle);
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using CUE4Parse_Conversion;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using FModel.Views.Snooper.Buffers;
|
||||
using FModel.Views.Snooper.Shading;
|
||||
|
||||
namespace FModel.Views.Snooper.Models;
|
||||
|
||||
public interface IRenderableModel : IExportableThing, 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 FBox Box { get; protected init; }
|
||||
public List<Socket> Sockets { get; }
|
||||
public List<Collision> Collisions { get; }
|
||||
public Material[] Materials { get; protected init; }
|
||||
public bool IsTwoSided { get; internal set; }
|
||||
public bool IsProp { get; internal set; }
|
||||
|
||||
public bool HasSockets { get; }
|
||||
public bool HasCollisions { get; }
|
||||
public int TransformsCount { get; }
|
||||
|
||||
public bool IsSetup { get; set; }
|
||||
public bool IsVisible { get; set; }
|
||||
public bool IsSelected { get; set; }
|
||||
public bool ShowWireframe { get; set; }
|
||||
public bool ShowCollisions { get; set; }
|
||||
public int SelectedInstance { get; set; }
|
||||
|
||||
public void Setup(Options options);
|
||||
public void SetupInstances();
|
||||
public void Render(Shader shader, Texture checker = null, bool outline = false);
|
||||
public void RenderCollision(Shader shader);
|
||||
public void PickingRender(Shader shader);
|
||||
public void Update(Options options);
|
||||
public void AddInstance(Transform transform);
|
||||
|
||||
public Transform GetTransform();
|
||||
}
|
||||
|
||||
public interface IExportableThing
|
||||
{
|
||||
public void AddToExportSession(ExportSession session);
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CUE4Parse.UE4.Assets.Exports.Animation;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
|
||||
namespace FModel.Views.Snooper.Models;
|
||||
|
||||
public class Morph : IDisposable
|
||||
{
|
||||
private int _handle;
|
||||
|
||||
public static readonly int VertexSize = 6; // Position + Tangent
|
||||
|
||||
public readonly string Name;
|
||||
public readonly float[] Vertices;
|
||||
|
||||
public Morph(float[] vertices, int vertexSize, UMorphTarget morphTarget)
|
||||
{
|
||||
Name = morphTarget.Name;
|
||||
Vertices = new float[vertices.Length / vertexSize * VertexSize];
|
||||
|
||||
bool TryFindVertex(uint index, out FVector positionDelta, out FVector tangentDelta)
|
||||
{
|
||||
foreach (var vertex in morphTarget.MorphLODModels[0].Vertices)
|
||||
{
|
||||
if (vertex.SourceIdx == index)
|
||||
{
|
||||
positionDelta = vertex.PositionDelta;
|
||||
tangentDelta = vertex.TangentZDelta;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
positionDelta = FVector.ZeroVector;
|
||||
tangentDelta = FVector.ZeroVector;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < vertices.Length; i += vertexSize)
|
||||
{
|
||||
var count = 0;
|
||||
var baseIndex = i / vertexSize * VertexSize;
|
||||
if (TryFindVertex((uint) vertices[i + 0], out var positionDelta, out var tangentDelta))
|
||||
{
|
||||
Vertices[baseIndex + count++] = vertices[i + 1] + positionDelta.X * Constants.SCALE_DOWN_RATIO;
|
||||
Vertices[baseIndex + count++] = vertices[i + 2] + positionDelta.Z * Constants.SCALE_DOWN_RATIO;
|
||||
Vertices[baseIndex + count++] = vertices[i + 3] + positionDelta.Y * Constants.SCALE_DOWN_RATIO;
|
||||
Vertices[baseIndex + count++] = vertices[i + 7] + tangentDelta.X;
|
||||
Vertices[baseIndex + count++] = vertices[i + 8] + tangentDelta.Z;
|
||||
Vertices[baseIndex + count++] = vertices[i + 9] + tangentDelta.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
Vertices[baseIndex + count++] = vertices[i + 1];
|
||||
Vertices[baseIndex + count++] = vertices[i + 2];
|
||||
Vertices[baseIndex + count++] = vertices[i + 3];
|
||||
Vertices[baseIndex + count++] = vertices[i + 7];
|
||||
Vertices[baseIndex + count++] = vertices[i + 8];
|
||||
Vertices[baseIndex + count++] = vertices[i + 9];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Morph(float[] vertices, Dictionary<uint, int> dict, UMorphTarget morphTarget, uint index = 0)
|
||||
{
|
||||
Name = morphTarget.Name;
|
||||
Vertices = new float[vertices.Length];
|
||||
Array.Copy(vertices, Vertices, vertices.Length);
|
||||
|
||||
foreach (var vert in morphTarget.MorphLODModels[index].Vertices)
|
||||
{
|
||||
var count = 0;
|
||||
if (dict.TryGetValue(vert.SourceIdx, out var baseIndex))
|
||||
{
|
||||
Vertices[baseIndex + count++] += vert.PositionDelta.X * Constants.SCALE_DOWN_RATIO;
|
||||
Vertices[baseIndex + count++] += vert.PositionDelta.Z * Constants.SCALE_DOWN_RATIO;
|
||||
Vertices[baseIndex + count++] += vert.PositionDelta.Y * Constants.SCALE_DOWN_RATIO;
|
||||
Vertices[baseIndex + count++] += vert.TangentZDelta.X;
|
||||
Vertices[baseIndex + count++] += vert.TangentZDelta.Z;
|
||||
Vertices[baseIndex + count++] += vert.TangentZDelta.Y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
_handle = GL.CreateProgram();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GL.DeleteProgram(_handle);
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using FModel.Views.Snooper.Shading;
|
||||
|
||||
namespace FModel.Views.Snooper.Models;
|
||||
|
||||
public class Section
|
||||
{
|
||||
public readonly int MaterialIndex;
|
||||
public readonly int FacesCount;
|
||||
public readonly int FirstFaceIndex;
|
||||
public readonly IntPtr FirstFaceIndexPtr;
|
||||
public readonly Vector3 Color;
|
||||
|
||||
public bool Show;
|
||||
|
||||
public Section(int index, int facesCount, int firstFaceIndex)
|
||||
{
|
||||
MaterialIndex = Math.Max(0, index);
|
||||
FacesCount = facesCount;
|
||||
FirstFaceIndex = firstFaceIndex;
|
||||
FirstFaceIndexPtr = new IntPtr(FirstFaceIndex * sizeof(uint));
|
||||
Color = Constants.COLOR_PALETTE[MaterialIndex % Constants.PALETTE_LENGTH];
|
||||
}
|
||||
|
||||
public void SetupMaterial(Material material)
|
||||
{
|
||||
material.IsUsed = true;
|
||||
Show = !material.Parameters.IsNull && !material.Parameters.IsTranslucent;
|
||||
}
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using CUE4Parse_Conversion.Dto;
|
||||
using CUE4Parse.UE4.Assets.Exports.Animation;
|
||||
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using CUE4Parse.UE4.Objects.PhysicsEngine;
|
||||
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<SkinnedMeshVertex>
|
||||
{
|
||||
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, SkeletalMeshDto skeletalMesh, Transform transform = null)
|
||||
: base(export, skeletalMesh.LODs[LodLevel], export.Materials, skeletalMesh.LODs[LodLevel].Vertices, skeletalMesh.LODs.Count, transform)
|
||||
{
|
||||
Box = skeletalMesh.Bounds * 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.Guid = skeleton.Guid;
|
||||
// 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));
|
||||
}
|
||||
|
||||
if (export.PhysicsAsset.TryLoad(out UPhysicsAsset physicsAsset))
|
||||
{
|
||||
foreach (var skeletalBodySetup in physicsAsset.SkeletalBodySetups)
|
||||
{
|
||||
if (!skeletalBodySetup.TryLoad(out USkeletalBodySetup bodySetup) || bodySetup.AggGeom == null) continue;
|
||||
foreach (var convexElem in bodySetup.AggGeom.ConvexElems)
|
||||
{
|
||||
Collisions.Add(new Collision(convexElem, bodySetup.BoneName));
|
||||
}
|
||||
foreach (var sphereElem in bodySetup.AggGeom.SphereElems)
|
||||
{
|
||||
Collisions.Add(new Collision(sphereElem, bodySetup.BoneName));
|
||||
}
|
||||
foreach (var boxElem in bodySetup.AggGeom.BoxElems)
|
||||
{
|
||||
Collisions.Add(new Collision(boxElem, bodySetup.BoneName));
|
||||
}
|
||||
foreach (var sphylElem in bodySetup.AggGeom.SphylElems)
|
||||
{
|
||||
Collisions.Add(new Collision(sphylElem, bodySetup.BoneName));
|
||||
}
|
||||
foreach (var taperedCapsuleElem in bodySetup.AggGeom.TaperedCapsuleElems)
|
||||
{
|
||||
Collisions.Add(new Collision(taperedCapsuleElem, bodySetup.BoneName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Morphs = [];
|
||||
if (export.MorphTargets.Length == 0) return;
|
||||
|
||||
export.PopulateMorphTargetVerticesData();
|
||||
|
||||
var verticesCount = Vertices.Length / VertexSize;
|
||||
var cachedVertices = new float[verticesCount * Morph.VertexSize];
|
||||
var vertexLookup = new Dictionary<uint, int>(verticesCount);
|
||||
for (int i = 0; i < Vertices.Length; i += VertexSize)
|
||||
{
|
||||
var count = 0;
|
||||
var baseIndex = i / VertexSize * Morph.VertexSize;
|
||||
vertexLookup[(uint) Vertices[i]] = baseIndex;
|
||||
{
|
||||
cachedVertices[baseIndex + count++] = Vertices[i + 1];
|
||||
cachedVertices[baseIndex + count++] = Vertices[i + 2];
|
||||
cachedVertices[baseIndex + count++] = Vertices[i + 3];
|
||||
cachedVertices[baseIndex + count++] = Vertices[i + 7];
|
||||
cachedVertices[baseIndex + count++] = Vertices[i + 8];
|
||||
cachedVertices[baseIndex + count++] = Vertices[i + 9];
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var morph in export.MorphTargets)
|
||||
{
|
||||
if (!morph.TryLoad(out UMorphTarget morphTarget) || morphTarget.MorphLODModels.Length <= skeletalMesh.LODs[LodLevel].SourceLodIndex ||
|
||||
morphTarget.MorphLODModels[skeletalMesh.LODs[LodLevel].SourceLodIndex].Vertices.Length < 1)
|
||||
continue;
|
||||
|
||||
Morphs.Add(new Morph(cachedVertices, vertexLookup, morphTarget, skeletalMesh.LODs[LodLevel].SourceLodIndex));
|
||||
}
|
||||
}
|
||||
|
||||
public SkeletalModel(USkeleton export, FBox box) : base(export)
|
||||
{
|
||||
Indices = [];
|
||||
Materials = [];
|
||||
Vertices = [];
|
||||
Sections = [];
|
||||
AddInstance(Transform.Identity);
|
||||
|
||||
Box = box * Constants.SCALE_DOWN_RATIO;
|
||||
Morphs = [];
|
||||
Skeleton = new Skeleton(export.ReferenceSkeleton);
|
||||
Skeleton.Name = export.Name;
|
||||
Skeleton.Guid = export.Guid;
|
||||
|
||||
for (int i = 0; i < export.Sockets.Length; i++)
|
||||
{
|
||||
if (export.Sockets[i].Load<USkeletalMeshSocket>() is not { } socket) continue;
|
||||
Sockets.Add(new Socket(socket));
|
||||
}
|
||||
|
||||
IsVisible = true;
|
||||
}
|
||||
|
||||
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 override void RenderCollision(Shader shader)
|
||||
{
|
||||
base.RenderCollision(shader);
|
||||
|
||||
GL.Disable(EnableCap.DepthTest);
|
||||
GL.Disable(EnableCap.CullFace);
|
||||
GL.PolygonMode(TriangleFace.FrontAndBack, PolygonMode.Line);
|
||||
foreach (var collision in Collisions)
|
||||
{
|
||||
var boneMatrix = Matrix4x4.Identity;
|
||||
if (Skeleton.BonesByLoweredName.TryGetValue(collision.LoweredBoneName, out var bone))
|
||||
boneMatrix = Skeleton.GetBoneMatrix(bone);
|
||||
|
||||
collision.Render(shader, boneMatrix);
|
||||
}
|
||||
GL.PolygonMode(TriangleFace.FrontAndBack, PolygonMode.Fill);
|
||||
GL.Enable(EnableCap.CullFace);
|
||||
GL.Enable(EnableCap.DepthTest);
|
||||
}
|
||||
|
||||
public void Render(Shader shader)
|
||||
{
|
||||
shader.SetUniform("uMorphTime", MorphTime);
|
||||
shader.SetUniform("uIsSpline", false);
|
||||
Skeleton.Render(shader);
|
||||
}
|
||||
|
||||
public void RenderBones(Shader shader)
|
||||
{
|
||||
shader.SetUniform("uInstanceMatrix", GetTransform().Matrix);
|
||||
Skeleton.RenderBones();
|
||||
}
|
||||
|
||||
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,115 +0,0 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using FModel.Views.Snooper.Buffers;
|
||||
using FModel.Views.Snooper.Shading;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
|
||||
namespace FModel.Views.Snooper.Models;
|
||||
|
||||
public class Skybox : IDisposable
|
||||
{
|
||||
private int _handle;
|
||||
|
||||
private BufferObject<uint> _ebo;
|
||||
private BufferObject<float> _vbo;
|
||||
private VertexArrayObject<float, uint> _vao;
|
||||
|
||||
private string[] _textures = { "px", "nx", "py", "ny", "pz", "nz" };
|
||||
|
||||
private Texture _cubeMap;
|
||||
private Shader _shader;
|
||||
|
||||
public readonly uint[] Indices = { 0, 1, 3, 1, 2, 3 };
|
||||
public readonly float[] Vertices = {
|
||||
//X Y Z
|
||||
-0.5f, -0.5f, -0.5f,
|
||||
0.5f, -0.5f, -0.5f,
|
||||
0.5f, 0.5f, -0.5f,
|
||||
0.5f, 0.5f, -0.5f,
|
||||
-0.5f, 0.5f, -0.5f,
|
||||
-0.5f, -0.5f, -0.5f,
|
||||
|
||||
-0.5f, -0.5f, 0.5f,
|
||||
0.5f, -0.5f, 0.5f,
|
||||
0.5f, 0.5f, 0.5f,
|
||||
0.5f, 0.5f, 0.5f,
|
||||
-0.5f, 0.5f, 0.5f,
|
||||
-0.5f, -0.5f, 0.5f,
|
||||
|
||||
-0.5f, 0.5f, 0.5f,
|
||||
-0.5f, 0.5f, -0.5f,
|
||||
-0.5f, -0.5f, -0.5f,
|
||||
-0.5f, -0.5f, -0.5f,
|
||||
-0.5f, -0.5f, 0.5f,
|
||||
-0.5f, 0.5f, 0.5f,
|
||||
|
||||
0.5f, 0.5f, 0.5f,
|
||||
0.5f, 0.5f, -0.5f,
|
||||
0.5f, -0.5f, -0.5f,
|
||||
0.5f, -0.5f, -0.5f,
|
||||
0.5f, -0.5f, 0.5f,
|
||||
0.5f, 0.5f, 0.5f,
|
||||
|
||||
-0.5f, -0.5f, -0.5f,
|
||||
0.5f, -0.5f, -0.5f,
|
||||
0.5f, -0.5f, 0.5f,
|
||||
0.5f, -0.5f, 0.5f,
|
||||
-0.5f, -0.5f, 0.5f,
|
||||
-0.5f, -0.5f, -0.5f,
|
||||
|
||||
-0.5f, 0.5f, -0.5f,
|
||||
0.5f, 0.5f, -0.5f,
|
||||
0.5f, 0.5f, 0.5f,
|
||||
0.5f, 0.5f, 0.5f,
|
||||
-0.5f, 0.5f, 0.5f,
|
||||
-0.5f, 0.5f, -0.5f
|
||||
};
|
||||
|
||||
public Skybox() {}
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
_handle = GL.CreateProgram();
|
||||
|
||||
_ebo = new BufferObject<uint>(Indices, BufferTarget.ElementArrayBuffer);
|
||||
_vbo = new BufferObject<float>(Vertices, BufferTarget.ArrayBuffer);
|
||||
_vao = new VertexArrayObject<float, uint>(_vbo, _ebo);
|
||||
|
||||
_cubeMap = new Texture(_textures);
|
||||
_shader = new Shader("skybox");
|
||||
|
||||
_vao.VertexAttributePointer(0, 3, VertexAttribPointerType.Float, 3, 0); // position
|
||||
}
|
||||
|
||||
public void Render(Matrix4x4 viewMatrix, Matrix4x4 projMatrix)
|
||||
{
|
||||
GL.Disable(EnableCap.CullFace);
|
||||
GL.DepthFunc(DepthFunction.Lequal);
|
||||
|
||||
_vao.Bind();
|
||||
_shader.Use();
|
||||
|
||||
viewMatrix.M41 = 0;
|
||||
viewMatrix.M42 = 0;
|
||||
viewMatrix.M43 = 0;
|
||||
_shader.SetUniform("uView", viewMatrix);
|
||||
_shader.SetUniform("uProjection", projMatrix);
|
||||
|
||||
_cubeMap.Bind(TextureUnit.Texture0);
|
||||
_shader.SetUniform("cubemap", 0);
|
||||
|
||||
GL.DrawArrays(PrimitiveType.Triangles, 0, 36);
|
||||
|
||||
GL.DepthFunc(DepthFunction.Less);
|
||||
GL.Enable(EnableCap.CullFace);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_ebo?.Dispose();
|
||||
_vbo?.Dispose();
|
||||
_vao?.Dispose();
|
||||
_shader?.Dispose();
|
||||
GL.DeleteProgram(_handle);
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
|
||||
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
|
||||
namespace FModel.Views.Snooper.Models;
|
||||
|
||||
public struct SocketAttachementInfo
|
||||
{
|
||||
public FGuid Guid;
|
||||
public int Instance;
|
||||
}
|
||||
|
||||
public class Socket : IDisposable
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly FName BoneName;
|
||||
public readonly Transform Transform;
|
||||
public readonly bool IsVirtual;
|
||||
|
||||
public readonly List<SocketAttachementInfo> AttachedModels;
|
||||
public bool IsDaron => AttachedModels.Count > 0;
|
||||
|
||||
private Socket()
|
||||
{
|
||||
Transform = Transform.Identity;
|
||||
AttachedModels = new List<SocketAttachementInfo>();
|
||||
}
|
||||
|
||||
public Socket(string name, FName boneName, Transform transform, bool isVirtual) : this()
|
||||
{
|
||||
Name = name;
|
||||
BoneName = boneName;
|
||||
Transform = transform;
|
||||
IsVirtual = isVirtual;
|
||||
}
|
||||
|
||||
public Socket(UStaticMeshSocket socket) : this()
|
||||
{
|
||||
Name = socket.SocketName.Text;
|
||||
Transform.Rotation = socket.RelativeRotation.Quaternion();
|
||||
Transform.Position = socket.RelativeLocation * Constants.SCALE_DOWN_RATIO;
|
||||
Transform.Scale = socket.RelativeScale;
|
||||
}
|
||||
|
||||
public Socket(USkeletalMeshSocket socket) : this()
|
||||
{
|
||||
Name = socket.SocketName.Text;
|
||||
BoneName = socket.BoneName;
|
||||
Transform.Rotation = socket.RelativeRotation.Quaternion();
|
||||
Transform.Position = socket.RelativeLocation * Constants.SCALE_DOWN_RATIO;
|
||||
Transform.Scale = socket.RelativeScale;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
AttachedModels.Clear();
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using CUE4Parse_Conversion.Dto;
|
||||
using CUE4Parse.UE4.Assets.Exports.Component.SplineMesh;
|
||||
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using FModel.Views.Snooper.Buffers;
|
||||
using FModel.Views.Snooper.Shading;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
|
||||
namespace FModel.Views.Snooper.Models;
|
||||
|
||||
public class SplineModel : StaticModel
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct GpuParams
|
||||
{
|
||||
public int ForwardAxis;
|
||||
public float SplineBoundaryMin;
|
||||
public float SplineBoundaryMax;
|
||||
public bool bSmoothInterpRollScale;
|
||||
|
||||
public FVector MeshOrigin;
|
||||
public int _padding0;
|
||||
public FVector MeshBoxExtent;
|
||||
public int _padding1;
|
||||
|
||||
public FVector StartPos;
|
||||
public float StartRoll;
|
||||
public FVector StartTangent;
|
||||
public int _padding2;
|
||||
public FVector2D StartScale;
|
||||
public FVector2D StartOffset;
|
||||
public FVector EndPos;
|
||||
public float EndRoll;
|
||||
public FVector EndTangent;
|
||||
public int _padding3;
|
||||
public FVector2D EndScale;
|
||||
public FVector2D EndOffset;
|
||||
|
||||
public FVector SplineUpDir;
|
||||
public int _padding4;
|
||||
|
||||
public GpuParams(USplineMeshComponent splineMesh)
|
||||
{
|
||||
ForwardAxis = (int)splineMesh.ForwardAxis;
|
||||
SplineBoundaryMin = splineMesh.SplineBoundaryMin;
|
||||
SplineBoundaryMax = splineMesh.SplineBoundaryMax;
|
||||
bSmoothInterpRollScale = splineMesh.bSmoothInterpRollScale;
|
||||
|
||||
var b = splineMesh.GetLoadedStaticMesh()?.RenderData?.Bounds ?? new FBoxSphereBounds();
|
||||
MeshOrigin = b.Origin * Constants.SCALE_DOWN_RATIO;
|
||||
MeshBoxExtent = b.BoxExtent * Constants.SCALE_DOWN_RATIO;
|
||||
|
||||
var p = splineMesh.SplineParams;
|
||||
StartPos = p.StartPos * Constants.SCALE_DOWN_RATIO;
|
||||
StartRoll = p.StartRoll;
|
||||
StartTangent = p.StartTangent * Constants.SCALE_DOWN_RATIO;
|
||||
StartScale = p.StartScale;
|
||||
StartOffset = p.StartOffset;
|
||||
EndPos = p.EndPos * Constants.SCALE_DOWN_RATIO;
|
||||
EndRoll = p.EndRoll;
|
||||
EndTangent = p.EndTangent * Constants.SCALE_DOWN_RATIO;
|
||||
EndScale = p.EndScale;
|
||||
EndOffset = p.EndOffset;
|
||||
|
||||
SplineUpDir = splineMesh.SplineUpDir;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<GpuParams> _splineParams;
|
||||
private BufferObject<GpuParams> _ssbo;
|
||||
|
||||
public SplineModel(UStaticMesh export, StaticMeshDto staticMesh, USplineMeshComponent splineMesh, Transform transform = null) : base(export, staticMesh, transform)
|
||||
{
|
||||
_splineParams = [new GpuParams(splineMesh)];
|
||||
|
||||
Type = "SplineMesh";
|
||||
IsVisible = true;
|
||||
IsTwoSided = true;
|
||||
}
|
||||
|
||||
public void AddComponent(USplineMeshComponent splineMesh)
|
||||
{
|
||||
_splineParams.Add(new GpuParams(splineMesh));
|
||||
}
|
||||
|
||||
public override void Setup(Options options)
|
||||
{
|
||||
base.Setup(options);
|
||||
|
||||
_ssbo = new BufferObject<GpuParams>(_splineParams.ToArray(), BufferTarget.ShaderStorageBuffer);
|
||||
}
|
||||
|
||||
public void Render(Shader shader)
|
||||
{
|
||||
shader.SetUniform("uIsSpline", true);
|
||||
_ssbo.BindBufferBase(3);
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
base.Dispose();
|
||||
_ssbo?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using CUE4Parse_Conversion.Dto;
|
||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using CUE4Parse.UE4.Objects.PhysicsEngine;
|
||||
using FModel.Views.Snooper.Shading;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
|
||||
namespace FModel.Views.Snooper.Models;
|
||||
|
||||
public class StaticModel : UModel<MeshVertex>
|
||||
{
|
||||
public StaticModel(UMaterialInterface unrealMaterial, StaticMeshDto staticMesh) : base(unrealMaterial)
|
||||
{
|
||||
var lod = staticMesh.LODs[LodLevel];
|
||||
|
||||
Indices = new uint[lod.Indices.Length];
|
||||
for (int i = 0; i < Indices.Length; i++)
|
||||
{
|
||||
Indices[i] = lod.Indices[i];
|
||||
}
|
||||
|
||||
Vertices = new float[lod.Vertices.Length * VertexSize];
|
||||
for (int i = 0; i < lod.Vertices.Length; i++)
|
||||
{
|
||||
var count = 0;
|
||||
var baseIndex = i * VertexSize;
|
||||
var vert = lod.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++] = .5f;
|
||||
}
|
||||
|
||||
Materials = new Material[1];
|
||||
Materials[0] = new Material(unrealMaterial) { IsUsed = true };
|
||||
|
||||
Sections = new Section[1];
|
||||
Sections[0] = new Section(0, Indices.Length, 0);
|
||||
|
||||
AddInstance(Transform.Identity);
|
||||
|
||||
Box = staticMesh.Bounds * 1.5f * Constants.SCALE_DOWN_RATIO;
|
||||
}
|
||||
|
||||
public StaticModel(UPaperSprite paperSprite, UTexture2D texture) : base(paperSprite)
|
||||
{
|
||||
Indices = new uint[paperSprite.BakedRenderData.Length];
|
||||
for (int i = 0; i < Indices.Length; i++)
|
||||
{
|
||||
Indices[i] = (uint) i;
|
||||
}
|
||||
|
||||
Vertices = new float[paperSprite.BakedRenderData.Length * VertexSize];
|
||||
for (int i = 0; i < paperSprite.BakedRenderData.Length; i++)
|
||||
{
|
||||
var count = 0;
|
||||
var baseIndex = i * VertexSize;
|
||||
var vert = paperSprite.BakedRenderData[i];
|
||||
var u = vert.Z;
|
||||
var v = vert.W;
|
||||
|
||||
Vertices[baseIndex + count++] = i;
|
||||
Vertices[baseIndex + count++] = vert.X * paperSprite.PixelsPerUnrealUnit * Constants.SCALE_DOWN_RATIO;
|
||||
Vertices[baseIndex + count++] = vert.Y * paperSprite.PixelsPerUnrealUnit * Constants.SCALE_DOWN_RATIO;
|
||||
Vertices[baseIndex + count++] = 0;
|
||||
Vertices[baseIndex + count++] = 0;
|
||||
Vertices[baseIndex + count++] = 0;
|
||||
Vertices[baseIndex + count++] = 0;
|
||||
Vertices[baseIndex + count++] = 0;
|
||||
Vertices[baseIndex + count++] = 0;
|
||||
Vertices[baseIndex + count++] = 0;
|
||||
Vertices[baseIndex + count++] = u;
|
||||
Vertices[baseIndex + count++] = v;
|
||||
Vertices[baseIndex + count++] = .5f;
|
||||
}
|
||||
|
||||
Materials = new Material[1];
|
||||
if (paperSprite.DefaultMaterial?.TryLoad(out UMaterialInstance unrealMaterial) ?? false)
|
||||
{
|
||||
Materials[0] = new Material(unrealMaterial);
|
||||
}
|
||||
else
|
||||
{
|
||||
Materials[0] = new Material();
|
||||
}
|
||||
Materials[0].Parameters.Textures[CMaterialParams2.FallbackDiffuse] = texture;
|
||||
Materials[0].IsUsed = true;
|
||||
|
||||
Sections = new Section[1];
|
||||
Sections[0] = new Section(0, Indices.Length, 0);
|
||||
|
||||
AddInstance(Transform.Identity);
|
||||
|
||||
var backward = new FVector(0, Math.Max(paperSprite.BakedSourceDimension.X, paperSprite.BakedSourceDimension.Y) / 2, 0);
|
||||
Box = new FBox(-backward, backward) * Constants.SCALE_DOWN_RATIO;
|
||||
}
|
||||
|
||||
public StaticModel(UStaticMesh export, StaticMeshDto staticMesh, Transform transform = null)
|
||||
: base(export, staticMesh.LODs[LodLevel], export.Materials, staticMesh.LODs[LodLevel].Vertices, staticMesh.LODs.Count, transform)
|
||||
{
|
||||
if (export.BodySetup.TryLoad(out UBodySetup bodySetup) && bodySetup.AggGeom != null)
|
||||
{
|
||||
foreach (var convexElem in bodySetup.AggGeom.ConvexElems)
|
||||
{
|
||||
Collisions.Add(new Collision(convexElem));
|
||||
}
|
||||
foreach (var sphereElem in bodySetup.AggGeom.SphereElems)
|
||||
{
|
||||
Collisions.Add(new Collision(sphereElem));
|
||||
}
|
||||
foreach (var boxElem in bodySetup.AggGeom.BoxElems)
|
||||
{
|
||||
Collisions.Add(new Collision(boxElem));
|
||||
}
|
||||
foreach (var sphylElem in bodySetup.AggGeom.SphylElems)
|
||||
{
|
||||
Collisions.Add(new Collision(sphylElem));
|
||||
}
|
||||
foreach (var taperedCapsuleElem in bodySetup.AggGeom.TaperedCapsuleElems)
|
||||
{
|
||||
Collisions.Add(new Collision(taperedCapsuleElem));
|
||||
}
|
||||
}
|
||||
|
||||
Box = staticMesh.Bounds * Constants.SCALE_DOWN_RATIO;
|
||||
foreach (var s in export.Sockets ?? [])
|
||||
{
|
||||
if (s.Load<UStaticMeshSocket>() is not { } socket) continue;
|
||||
Sockets.Add(new Socket(socket));
|
||||
}
|
||||
}
|
||||
|
||||
public override void RenderCollision(Shader shader)
|
||||
{
|
||||
base.RenderCollision(shader);
|
||||
|
||||
GL.Disable(EnableCap.CullFace);
|
||||
GL.PolygonMode(TriangleFace.FrontAndBack, PolygonMode.Line);
|
||||
foreach (var collision in Collisions)
|
||||
{
|
||||
collision.Render(shader, Matrix4x4.Identity);
|
||||
}
|
||||
GL.PolygonMode(TriangleFace.FrontAndBack, PolygonMode.Fill);
|
||||
GL.Enable(EnableCap.CullFace);
|
||||
}
|
||||
}
|
||||
@@ -1,416 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using CUE4Parse_Conversion;
|
||||
using CUE4Parse_Conversion.Dto;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
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 VertexAttribPointerType Type;
|
||||
public bool Enabled;
|
||||
}
|
||||
|
||||
public abstract class UModel<TVertex> : IRenderableModel where TVertex : struct, IMeshVertex
|
||||
{
|
||||
protected const int LodLevel = 0;
|
||||
|
||||
private readonly UObject _export;
|
||||
private readonly List<VertexAttribute> _vertexAttributes =
|
||||
[
|
||||
new VertexAttribute { Size = 1, Type = VertexAttribPointerType.Int, Enabled = false }, // VertexIndex
|
||||
new VertexAttribute { Size = 3, Type = VertexAttribPointerType.Float, Enabled = true }, // Position
|
||||
new VertexAttribute { Size = 3, Type = VertexAttribPointerType.Float, Enabled = false }, // Normal
|
||||
new VertexAttribute { Size = 3, Type = VertexAttribPointerType.Float, Enabled = false }, // Tangent
|
||||
new VertexAttribute { Size = 2, Type = VertexAttribPointerType.Float, Enabled = false }, // UV
|
||||
new VertexAttribute { Size = 1, Type = VertexAttribPointerType.Float, Enabled = false }, // TextureLayer
|
||||
new VertexAttribute { Size = 1, Type = VertexAttribPointerType.Float, Enabled = false }, // Colors
|
||||
new VertexAttribute { Size = 4, Type = VertexAttribPointerType.Float, Enabled = false }, // BoneIds
|
||||
new VertexAttribute { Size = 4, Type = VertexAttribPointerType.Float, 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; protected set; }
|
||||
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 { get; init; }
|
||||
public List<Socket> Sockets { get; }
|
||||
public List<Collision> Collisions { get; }
|
||||
public Material[] Materials { get; init; }
|
||||
public bool IsTwoSided { get; set; }
|
||||
public bool IsProp { get; set; }
|
||||
|
||||
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 bool HasCollisions => Collisions.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 bool ShowCollisions { get; set; }
|
||||
public int SelectedInstance { get; set; }
|
||||
|
||||
protected UModel()
|
||||
{
|
||||
_export = null;
|
||||
UvCount = 1;
|
||||
|
||||
Box = new FBox(new FVector(-2f), new FVector(2f));
|
||||
Sockets = new List<Socket>();
|
||||
Collisions = new List<Collision>();
|
||||
Transforms = new List<Transform>();
|
||||
}
|
||||
|
||||
protected UModel(UObject export)
|
||||
{
|
||||
_export = export;
|
||||
Path = _export.GetPathName();
|
||||
Name = export.Name;
|
||||
Type = export.ExportType;
|
||||
UvCount = 1;
|
||||
|
||||
Box = new FBox(new FVector(-2f), new FVector(2f));
|
||||
Sockets = new List<Socket>();
|
||||
Collisions = new List<Collision>();
|
||||
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, MeshLodDto<TVertex> lod, IReadOnlyList<FPackageIndex> materials, IReadOnlyList<TVertex> vertices, int numLods, Transform transform = null) : this(export)
|
||||
{
|
||||
var hasCustomUvs = lod.ExtraUvs.Length > 0;
|
||||
UvCount = hasCustomUvs ? Math.Max(lod.ExtraUvs.Length, numLods) : lod.ExtraUvs.Length + 1;
|
||||
IsTwoSided = lod.IsTwoSided;
|
||||
|
||||
Indices = new uint[lod.Indices.Length];
|
||||
for (int i = 0; i < Indices.Length; i++)
|
||||
{
|
||||
Indices[i] = lod.Indices[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 SkinnedMeshVertex[];
|
||||
|
||||
Vertices = new float[vertices.Count * 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.ExtraUvs[0][i].U - 1 : .5f;
|
||||
|
||||
if (HasVertexColors)
|
||||
{
|
||||
Vertices[baseIndex + count++] = lod.VertexColors![0].Colors[i].ToPackedARGB();
|
||||
}
|
||||
|
||||
if (vert is SkinnedMeshVertex skelVert)
|
||||
{
|
||||
int max = skelVert.Influences.Length;
|
||||
for (int j = 0; j < 8; j++)
|
||||
{
|
||||
var boneID = j < max ? skelVert.Influences[j].Bone : (ushort) 0;
|
||||
var weight = j < max ? skelVert.Influences[j].RawWeight : (ushort) 0;
|
||||
|
||||
// Pack bone ID and weight
|
||||
Vertices[baseIndex + count++] = (boneID << 16) | weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Sections = new Section[lod.Sections.Length];
|
||||
for (var s = 0; s < Sections.Length; s++)
|
||||
{
|
||||
var section = lod.Sections[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, attribute.Type, 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);
|
||||
}
|
||||
|
||||
foreach (var collision in Collisions)
|
||||
{
|
||||
collision.Setup();
|
||||
}
|
||||
|
||||
if (options.Models.Count == 1 && Sections.All(x => !x.Show)) // visible if alone and invisible
|
||||
{
|
||||
IsVisible = true;
|
||||
foreach (var section in Sections)
|
||||
{
|
||||
section.Show = true;
|
||||
}
|
||||
}
|
||||
else if (!IsVisible) // default: visible if one section is visible
|
||||
{
|
||||
foreach (var section in Sections)
|
||||
{
|
||||
if (section.Show)
|
||||
{
|
||||
IsVisible = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else foreach (var section in Sections) // force visibility
|
||||
{
|
||||
section.Show = true;
|
||||
}
|
||||
|
||||
IsSetup = true;
|
||||
}
|
||||
|
||||
public virtual void Render(Shader shader, Texture checker = null, 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 if (this is SplineModel splineModel) splineModel.Render(shader);
|
||||
else
|
||||
{
|
||||
shader.SetUniform("uIsAnimated", false);
|
||||
shader.SetUniform("uIsSpline", false);
|
||||
}
|
||||
|
||||
if (!outline)
|
||||
{
|
||||
shader.SetUniform("uUvCount", UvCount);
|
||||
shader.SetUniform("uOpacity", ShowCollisions && IsSelected ? 0.75f : 1f);
|
||||
shader.SetUniform("uHasVertexColors", HasVertexColors);
|
||||
}
|
||||
|
||||
Vao.Bind();
|
||||
GL.PolygonMode(TriangleFace.FrontAndBack, ShowWireframe ? PolygonMode.Line : PolygonMode.Fill);
|
||||
foreach (var section in Sections)
|
||||
{
|
||||
if (!section.Show) continue;
|
||||
if (!outline)
|
||||
{
|
||||
if (checker != null)
|
||||
{
|
||||
shader.SetUniform("uParameters.Diffuse[0].Sampler", 0);
|
||||
checker.Bind(TextureUnit.Texture0);
|
||||
}
|
||||
else
|
||||
{
|
||||
shader.SetUniform("uSectionColor", section.Color);
|
||||
Materials[section.MaterialIndex].Render(shader);
|
||||
}
|
||||
}
|
||||
|
||||
GL.DrawElementsInstanced(PrimitiveType.Triangles, section.FacesCount, DrawElementsType.UnsignedInt, section.FirstFaceIndexPtr, TransformsCount);
|
||||
}
|
||||
GL.PolygonMode(TriangleFace.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);
|
||||
if (this is SplineModel splineModel) splineModel.Render(shader);
|
||||
else
|
||||
{
|
||||
shader.SetUniform("uIsAnimated", false);
|
||||
shader.SetUniform("uIsSpline", 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 virtual void RenderCollision(Shader shader)
|
||||
{
|
||||
shader.SetUniform("uInstanceMatrix", GetTransform().Matrix);
|
||||
shader.SetUniform("uScaleDown", Constants.SCALE_DOWN_RATIO);
|
||||
}
|
||||
|
||||
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++)
|
||||
{
|
||||
Transforms[instance].Save();
|
||||
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 void AddToExportSession(ExportSession session)
|
||||
{
|
||||
session.Add(_export);
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
Ebo?.Dispose();
|
||||
Vbo?.Dispose();
|
||||
MatrixVbo?.Dispose();
|
||||
Vao?.Dispose();
|
||||
foreach (var socket in Sockets)
|
||||
{
|
||||
socket?.Dispose();
|
||||
}
|
||||
Sockets.Clear();
|
||||
foreach (var collision in Collisions)
|
||||
{
|
||||
collision?.Dispose();
|
||||
}
|
||||
Collisions.Clear();
|
||||
|
||||
GL.DeleteProgram(Handle);
|
||||
}
|
||||
}
|
||||
@@ -1,285 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using CUE4Parse_Conversion.Textures;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using FModel.Settings;
|
||||
using FModel.Views.Snooper.Animations;
|
||||
using FModel.Views.Snooper.Lights;
|
||||
using FModel.Views.Snooper.Models;
|
||||
using FModel.Views.Snooper.Shading;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Views.Snooper;
|
||||
|
||||
public class Options
|
||||
{
|
||||
public FGuid SelectedModel { get; private set; }
|
||||
public int SelectedSection { get; private set; }
|
||||
public int SelectedMorph { get; private set; }
|
||||
public int SelectedAnimation{ get; private set; }
|
||||
|
||||
public readonly Dictionary<FGuid, IRenderableModel> Models;
|
||||
public readonly Dictionary<FGuid, Texture> Textures;
|
||||
public readonly List<Light> Lights;
|
||||
|
||||
public readonly TimeTracker Tracker;
|
||||
public readonly List<Animation> Animations;
|
||||
|
||||
public readonly Dictionary<string, Texture> Icons;
|
||||
|
||||
private readonly string _game;
|
||||
|
||||
public Options()
|
||||
{
|
||||
Models = new Dictionary<FGuid, IRenderableModel>();
|
||||
Textures = new Dictionary<FGuid, Texture>();
|
||||
Lights = new List<Light>();
|
||||
|
||||
Tracker = new TimeTracker();
|
||||
Animations = new List<Animation>();
|
||||
|
||||
Icons = new Dictionary<string, Texture>
|
||||
{
|
||||
["material"] = new ("materialicon"),
|
||||
["square"] = new ("square"),
|
||||
["square_off"] = new ("square_off"),
|
||||
["cube"] = new ("cube"),
|
||||
["cube_off"] = new ("cube_off"),
|
||||
["light"] = new ("light"),
|
||||
["light_off"] = new ("light_off"),
|
||||
["noimage"] = new ("T_Placeholder_Item_Image"),
|
||||
["checker"] = new ("checker"),
|
||||
["pointlight"] = new ("pointlight"),
|
||||
["spotlight"] = new ("spotlight"),
|
||||
["link_on"] = new ("link_on"),
|
||||
["link_off"] = new ("link_off"),
|
||||
["link_has"] = new ("link_has"),
|
||||
["tl_play"] = new ("tl_play"),
|
||||
["tl_pause"] = new ("tl_pause"),
|
||||
["tl_rewind"] = new ("tl_rewind"),
|
||||
["tl_forward"] = new ("tl_forward"),
|
||||
["tl_previous"] = new ("tl_previous"),
|
||||
["tl_next"] = new ("tl_next"),
|
||||
};
|
||||
|
||||
_game = Services.ApplicationService.ApplicationView.CUE4Parse.Provider.ProjectName.ToUpper();
|
||||
|
||||
SelectModel(Guid.Empty);
|
||||
}
|
||||
|
||||
public void SetupModelsAndLights()
|
||||
{
|
||||
foreach (var model in Models.Values)
|
||||
{
|
||||
if (model.IsSetup) continue;
|
||||
model.Setup(this);
|
||||
}
|
||||
|
||||
foreach (var light in Lights)
|
||||
{
|
||||
if (light.IsSetup) continue;
|
||||
light.Setup();
|
||||
}
|
||||
}
|
||||
|
||||
public void SelectModel(FGuid guid)
|
||||
{
|
||||
// unselect old
|
||||
if (TryGetModel(out var model))
|
||||
model.IsSelected = false;
|
||||
|
||||
// select new
|
||||
if (!TryGetModel(guid, out model))
|
||||
SelectedModel = Guid.Empty;
|
||||
else
|
||||
{
|
||||
model.IsSelected = true;
|
||||
SelectedModel = guid;
|
||||
}
|
||||
|
||||
SelectedSection = 0;
|
||||
SelectedMorph = 0;
|
||||
}
|
||||
|
||||
public void SelectAnimation(int animation)
|
||||
{
|
||||
SelectedAnimation = animation;
|
||||
}
|
||||
|
||||
public void RemoveModel(FGuid guid)
|
||||
{
|
||||
if (!TryGetModel(guid, out var m) || m is null)
|
||||
return;
|
||||
|
||||
DetachAndRemoveModels(m, true);
|
||||
m.Dispose();
|
||||
Models.Remove(guid);
|
||||
}
|
||||
|
||||
private void DetachAndRemoveModels(IRenderableModel model, bool detach)
|
||||
{
|
||||
foreach (var socket in model.Sockets.ToList())
|
||||
{
|
||||
foreach (var info in socket.AttachedModels)
|
||||
{
|
||||
if (!TryGetModel(info.Guid, out var m) || m is null)
|
||||
continue;
|
||||
|
||||
var t = m.GetTransform();
|
||||
if (m.IsProp)
|
||||
{
|
||||
m.Attachments.SafeDetach(model, t);
|
||||
RemoveModel(info.Guid);
|
||||
}
|
||||
else if (detach) m.Attachments.SafeDetach(model, t);
|
||||
}
|
||||
|
||||
if (socket.IsVirtual)
|
||||
{
|
||||
socket.Dispose();
|
||||
model.Sockets.Remove(socket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddAnimation(Animation animation)
|
||||
{
|
||||
Animations.Add(animation);
|
||||
}
|
||||
|
||||
public void RemoveAnimations()
|
||||
{
|
||||
Tracker.Reset();
|
||||
SelectedAnimation = 0;
|
||||
|
||||
foreach (var animation in Animations)
|
||||
{
|
||||
foreach (var guid in animation.AttachedModels)
|
||||
{
|
||||
if (!TryGetModel(guid, out var model) || model is not SkeletalModel animatedModel)
|
||||
continue;
|
||||
|
||||
animatedModel.Skeleton.ResetAnimatedData(true);
|
||||
DetachAndRemoveModels(animatedModel, false);
|
||||
}
|
||||
|
||||
animation.Dispose();
|
||||
}
|
||||
|
||||
foreach (var kvp in Models)
|
||||
if (kvp.Value.IsProp)
|
||||
RemoveModel(kvp.Key);
|
||||
|
||||
Animations.Clear();
|
||||
}
|
||||
|
||||
public void SelectSection(int index)
|
||||
{
|
||||
SelectedSection = index;
|
||||
}
|
||||
|
||||
public void SelectMorph(int index, SkeletalModel model)
|
||||
{
|
||||
SelectedMorph = index;
|
||||
model.UpdateMorph(SelectedMorph);
|
||||
}
|
||||
|
||||
public bool TryGetTexture(UTexture o, bool fix, out Texture texture)
|
||||
{
|
||||
var guid = o.LightingGuid;
|
||||
if (Textures.TryGetValue(guid, out texture)) return texture != null;
|
||||
|
||||
var bitmap = o switch
|
||||
{
|
||||
UTexture2D texture2D => texture2D.Decode(UserSettings.Default.PreviewMaxTextureSize, UserSettings.Default.CurrentDir.TexturePlatform),
|
||||
UTexture2DArray texture2DArray => texture2DArray.DecodeTextureArray(UserSettings.Default.CurrentDir.TexturePlatform)?.FirstOrDefault(),
|
||||
_ => o.Decode(UserSettings.Default.CurrentDir.TexturePlatform)
|
||||
};
|
||||
|
||||
if (bitmap is not null)
|
||||
{
|
||||
texture = new Texture(bitmap.ToSkBitmap(), o);
|
||||
if (fix) TextureHelper.FixChannels(_game, texture);
|
||||
Textures[guid] = texture;
|
||||
}
|
||||
|
||||
return texture != null;
|
||||
}
|
||||
|
||||
public bool TryGetModel([MaybeNullWhen(false)] out IRenderableModel model) => Models.TryGetValue(SelectedModel, out model);
|
||||
public bool TryGetModel(FGuid guid, [MaybeNullWhen(false)] out IRenderableModel model) => Models.TryGetValue(guid, out model);
|
||||
|
||||
public bool TryGetSection(out Section section) => TryGetSection(SelectedModel, out section);
|
||||
public bool TryGetSection(FGuid guid, out Section section)
|
||||
{
|
||||
if (TryGetModel(guid, out var model))
|
||||
{
|
||||
return TryGetSection(model, out section);
|
||||
}
|
||||
|
||||
section = null;
|
||||
return false;
|
||||
}
|
||||
public bool TryGetSection(IRenderableModel model, out Section section)
|
||||
{
|
||||
if (SelectedSection >= 0 && SelectedSection < model.Sections.Length)
|
||||
section = model.Sections[SelectedSection]; else section = null;
|
||||
return section != null;
|
||||
}
|
||||
|
||||
public void SwapMaterial(bool value)
|
||||
{
|
||||
Services.ApplicationService.ApplicationView.CUE4Parse.ModelIsOverwritingMaterial = value;
|
||||
}
|
||||
|
||||
public void AnimateMesh(bool value)
|
||||
{
|
||||
Services.ApplicationService.ApplicationView.CUE4Parse.ModelIsWaitingAnimation = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Skip emissive for specific games, cause of excessive use in their materials
|
||||
/// </summary>
|
||||
public bool SkipEmissive()
|
||||
{
|
||||
return _game switch
|
||||
{
|
||||
"LIESOFP" or "CODEVEIN2" or "HIGHONLIFE2" or "MORTALSHELL2" => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
public void ResetModelsLightsAnimations()
|
||||
{
|
||||
foreach (var model in Models.Values)
|
||||
{
|
||||
model.Dispose();
|
||||
}
|
||||
Models.Clear();
|
||||
Lights.Clear();
|
||||
Tracker.Reset();
|
||||
foreach (var animation in Animations)
|
||||
{
|
||||
animation.Dispose();
|
||||
}
|
||||
Animations.Clear();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ResetModelsLightsAnimations();
|
||||
foreach (var texture in Textures.Values)
|
||||
{
|
||||
texture.Dispose();
|
||||
}
|
||||
Textures.Clear();
|
||||
foreach (var texture in Icons.Values)
|
||||
{
|
||||
texture.Dispose();
|
||||
}
|
||||
Icons.Clear();
|
||||
}
|
||||
}
|
||||
@@ -1,816 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
using CUE4Parse_Conversion.Animations;
|
||||
using CUE4Parse_Conversion.Meshes;
|
||||
using CUE4Parse_Conversion.Options;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports.Animation;
|
||||
using CUE4Parse.UE4.Assets.Exports.Component.SplineMesh;
|
||||
using CUE4Parse.UE4.Assets.Exports.Component.StaticMesh;
|
||||
using CUE4Parse.UE4.Assets.Exports.GeometryCollection;
|
||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
|
||||
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using CUE4Parse.UE4.Objects.Engine;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using CUE4Parse.Utils;
|
||||
using FModel.Creator;
|
||||
using FModel.Settings;
|
||||
using FModel.Views.Snooper.Animations;
|
||||
using FModel.Views.Snooper.Buffers;
|
||||
using FModel.Views.Snooper.Lights;
|
||||
using FModel.Views.Snooper.Models;
|
||||
using FModel.Views.Snooper.Shading;
|
||||
using OpenTK.Windowing.GraphicsLibraryFramework;
|
||||
|
||||
namespace FModel.Views.Snooper;
|
||||
|
||||
public enum VertexColor
|
||||
{
|
||||
Default,
|
||||
Sections,
|
||||
Colors,
|
||||
Normals,
|
||||
TextureCoordinates
|
||||
}
|
||||
|
||||
public class Renderer : IDisposable
|
||||
{
|
||||
private readonly Skybox _skybox;
|
||||
private readonly Grid _grid;
|
||||
private Shader _shader;
|
||||
private Shader _outline;
|
||||
private Shader _light;
|
||||
private Shader _bone;
|
||||
private Shader _collision;
|
||||
private bool _saveCameraMode;
|
||||
|
||||
public bool ShowSkybox;
|
||||
public bool ShowGrid;
|
||||
public bool ShowLights;
|
||||
public bool AnimateWithRotationOnly;
|
||||
public bool IsSkeletonTreeOpen;
|
||||
public VertexColor Color;
|
||||
|
||||
public Camera CameraOp { get; }
|
||||
public PickingTexture Picking { get; }
|
||||
public Options Options { get; }
|
||||
|
||||
public Renderer(int width, int height)
|
||||
{
|
||||
_skybox = new Skybox();
|
||||
_grid = new Grid();
|
||||
|
||||
CameraOp = new Camera();
|
||||
Picking = new PickingTexture(width, height);
|
||||
Options = new Options();
|
||||
|
||||
ShowSkybox = UserSettings.Default.ShowSkybox;
|
||||
ShowGrid = UserSettings.Default.ShowGrid;
|
||||
AnimateWithRotationOnly = UserSettings.Default.AnimateWithRotationOnly;
|
||||
Color = VertexColor.Default;
|
||||
}
|
||||
|
||||
public void Load(CancellationToken cancellationToken, UObject dummy, Lazy<UObject> export)
|
||||
{
|
||||
ShowLights = false;
|
||||
Color = VertexColor.Default;
|
||||
_saveCameraMode = dummy is not UWorld and not UBlueprintGeneratedClass;
|
||||
switch (dummy)
|
||||
{
|
||||
case UStaticMesh when export.Value is UStaticMesh st:
|
||||
LoadStaticMesh(st, UserSettings.Default.NaniteMeshExportFormat);
|
||||
break;
|
||||
case USkeletalMesh when export.Value is USkeletalMesh sk:
|
||||
LoadSkeletalMesh(sk);
|
||||
break;
|
||||
case USkeleton when export.Value is USkeleton skel:
|
||||
LoadSkeleton(skel);
|
||||
break;
|
||||
case UMaterialInstance when export.Value is UMaterialInstance mi:
|
||||
LoadMaterialInstance(mi);
|
||||
break;
|
||||
case UWorld when export.Value is UWorld wd:
|
||||
LoadWorld(cancellationToken, wd, Transform.Identity);
|
||||
break;
|
||||
case UBlueprintGeneratedClass when export.Value is UBlueprintGeneratedClass bp:
|
||||
LoadJunoWorld(cancellationToken, bp, Transform.Identity);
|
||||
Color = VertexColor.Colors;
|
||||
break;
|
||||
case UPaperSprite when export.Value is UPaperSprite ps:
|
||||
LoadPaperSprite(ps);
|
||||
break;
|
||||
}
|
||||
CameraOp.Mode = _saveCameraMode ? UserSettings.Default.CameraMode : Camera.WorldMode.FlyCam;
|
||||
SetupCamera();
|
||||
}
|
||||
|
||||
public void Swap(UMaterialInstance unrealMaterial)
|
||||
{
|
||||
if (!Options.TryGetModel(out var model) || !Options.TryGetSection(model, out var section)) return;
|
||||
|
||||
model.Materials[section.MaterialIndex].SwapMaterial(unrealMaterial);
|
||||
Application.Current.Dispatcher.Invoke(() => model.Materials[section.MaterialIndex].Setup(Options, model.UvCount));
|
||||
}
|
||||
|
||||
public void Animate(UObject anim)
|
||||
{
|
||||
if (!Services.ApplicationService.ApplicationView.CUE4Parse.ModelIsWaitingAnimation)
|
||||
{
|
||||
if (anim is UAnimSequenceBase animBase)
|
||||
{
|
||||
/*if (Options.TryGetModel(out var selected) &&
|
||||
selected is SkeletalModel { IsVisible: true } skeletalModel &&
|
||||
skeletalModel.Skeleton.Guid == animBase.SkeletonGuid)
|
||||
{
|
||||
// do nothing, selected model has the correct skeleton for this animation
|
||||
}
|
||||
else */if (animBase.Skeleton.TryLoad(out USkeleton skeleton))
|
||||
{
|
||||
LoadSkeleton(skeleton);
|
||||
}
|
||||
}
|
||||
else return; // should never end here
|
||||
}
|
||||
|
||||
Animate(anim, Options.SelectedModel);
|
||||
}
|
||||
private void Animate(UObject anim, FGuid guid)
|
||||
{
|
||||
if (anim is not UAnimSequenceBase animBase || !animBase.Skeleton.TryLoad(out USkeleton skeleton) ||
|
||||
!Options.TryGetModel(guid, out var m) || m is not SkeletalModel model)
|
||||
return;
|
||||
|
||||
var animSet = animBase switch
|
||||
{
|
||||
UAnimSequence animSequence => skeleton.ConvertAnims(animSequence),
|
||||
UAnimMontage animMontage => skeleton.ConvertAnims(animMontage),
|
||||
UAnimComposite animComposite => skeleton.ConvertAnims(animComposite),
|
||||
_ => throw new ArgumentException("Unknown animation type")
|
||||
};
|
||||
|
||||
var animation = new Animation(anim, animSet, guid);
|
||||
model.Skeleton.Animate(animSet);
|
||||
Options.AddAnimation(animation);
|
||||
|
||||
foreach (var notifyEvent in animBase.Notifies ?? [])
|
||||
{
|
||||
if (notifyEvent.NotifyStateClass is null ||
|
||||
!notifyEvent.NotifyStateClass.TryLoad(out UObject notifyClass) ||
|
||||
!notifyClass.TryGetValue(out UObject export, "SkeletalMeshProp", "StaticMeshProp", "Mesh", "SkeletalMeshTemplate"))
|
||||
continue;
|
||||
|
||||
var t = Transform.Identity;
|
||||
if (notifyClass.TryGetValue(out FTransform offset, "Offset"))
|
||||
{
|
||||
t.Rotation = offset.Rotation;
|
||||
t.Position = offset.Translation * Constants.SCALE_DOWN_RATIO;
|
||||
t.Scale = offset.Scale3D;
|
||||
}
|
||||
|
||||
IRenderableModel addedModel = null;
|
||||
switch (export)
|
||||
{
|
||||
case UStaticMesh st:
|
||||
{
|
||||
guid = st.LightingGuid;
|
||||
if (Options.TryGetModel(guid, out addedModel))
|
||||
{
|
||||
addedModel.AddInstance(t);
|
||||
}
|
||||
else if (st.TryConvert(out var mesh, EMeshQuality.Highest))
|
||||
{
|
||||
addedModel = new StaticModel(st, mesh, t);
|
||||
Options.Models[guid] = addedModel;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case USkeletalMesh sk:
|
||||
{
|
||||
guid = Guid.NewGuid();
|
||||
if (!Options.Models.ContainsKey(guid) && sk.TryConvert(out var mesh, EMeshQuality.Highest))
|
||||
{
|
||||
addedModel = new SkeletalModel(sk, mesh, t);
|
||||
Options.Models[guid] = addedModel;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (addedModel == null)
|
||||
throw new ArgumentException("Unknown model type");
|
||||
|
||||
addedModel.IsProp = true;
|
||||
if (notifyClass.TryGetValue(out UObject skeletalMeshPropAnimation, "SkeletalMeshPropAnimation", "Animation", "AnimToPlay"))
|
||||
Animate(skeletalMeshPropAnimation, guid);
|
||||
if (notifyClass.TryGetValue(out FName socketName, "SocketName"))
|
||||
{
|
||||
t = Transform.Identity;
|
||||
if (notifyClass.TryGetValue(out FVector location, "LocationOffset", "Location"))
|
||||
t.Position = location * Constants.SCALE_DOWN_RATIO;
|
||||
if (notifyClass.TryGetValue(out FRotator rotation, "RotationOffset", "Rotation"))
|
||||
t.Rotation = rotation.Quaternion();
|
||||
if (notifyClass.TryGetValue(out FVector scale, "Scale"))
|
||||
t.Scale = scale;
|
||||
|
||||
var s = new Socket($"ANIM_{addedModel.Name}", socketName, t, true);
|
||||
model.Sockets.Add(s);
|
||||
addedModel.Attachments.Attach(model, addedModel.GetTransform(), s,
|
||||
new SocketAttachementInfo { Guid = guid, Instance = addedModel.SelectedInstance });
|
||||
}
|
||||
}
|
||||
|
||||
Options.Tracker.IsPaused = false;
|
||||
Options.Tracker.SafeSetMaxElapsedTime(animation.TotalElapsedTime);
|
||||
}
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
_skybox.Setup();
|
||||
_grid.Setup();
|
||||
|
||||
_shader = new Shader();
|
||||
_outline = new Shader("outline");
|
||||
_light = new Shader("light");
|
||||
_bone = new Shader("bone");
|
||||
_collision = new Shader("collision", "bone");
|
||||
|
||||
Picking.Setup();
|
||||
Options.SetupModelsAndLights();
|
||||
}
|
||||
|
||||
public void Render()
|
||||
{
|
||||
var viewMatrix = CameraOp.GetViewMatrix();
|
||||
var projMatrix = CameraOp.GetProjectionMatrix();
|
||||
|
||||
if (ShowSkybox) _skybox.Render(viewMatrix, projMatrix);
|
||||
if (ShowGrid) _grid.Render(viewMatrix, projMatrix, CameraOp.Near, CameraOp.Far);
|
||||
|
||||
_shader.Render(viewMatrix, CameraOp.Position, projMatrix);
|
||||
for (int i = 0; i < 5; i++)
|
||||
_shader.SetUniform($"bVertexColors[{i}]", i == (int) Color);
|
||||
|
||||
// render model pass
|
||||
foreach (var model in Options.Models.Values)
|
||||
{
|
||||
if (!model.IsVisible) continue;
|
||||
model.Render(_shader, Color == VertexColor.TextureCoordinates ? Options.Icons["checker"] : null);
|
||||
}
|
||||
|
||||
{ // light pass
|
||||
var uNumLights = Math.Min(Options.Lights.Count, 100);
|
||||
_shader.SetUniform("uNumLights", ShowLights ? uNumLights : 0);
|
||||
|
||||
if (ShowLights)
|
||||
for (int i = 0; i < uNumLights; i++)
|
||||
Options.Lights[i].Render(i, _shader);
|
||||
|
||||
_light.Render(viewMatrix, projMatrix);
|
||||
for (int i = 0; i < uNumLights; i++)
|
||||
Options.Lights[i].Render(_light);
|
||||
}
|
||||
|
||||
// debug + outline pass
|
||||
if (Options.TryGetModel(out var selected) && selected.IsVisible)
|
||||
{
|
||||
if (IsSkeletonTreeOpen && selected is SkeletalModel skeletalModel)
|
||||
{
|
||||
_bone.Render(viewMatrix, projMatrix);
|
||||
skeletalModel.RenderBones(_bone);
|
||||
}
|
||||
else if (selected.ShowCollisions)
|
||||
{
|
||||
_collision.Render(viewMatrix, projMatrix);
|
||||
selected.RenderCollision(_collision);
|
||||
}
|
||||
|
||||
_outline.Render(viewMatrix, CameraOp.Position, projMatrix);
|
||||
selected.Render(_outline, Color == VertexColor.TextureCoordinates ? Options.Icons["checker"] : null, true);
|
||||
}
|
||||
|
||||
// picking pass (dedicated FBO, binding to 0 afterward)
|
||||
Picking.Render(viewMatrix, projMatrix, Options.Models);
|
||||
}
|
||||
|
||||
public void Update(Snooper wnd, float deltaSeconds)
|
||||
{
|
||||
if (Options.Animations.Count > 0) Options.Tracker.Update(deltaSeconds);
|
||||
foreach (var animation in Options.Animations)
|
||||
{
|
||||
animation.TimeCalculation(Options.Tracker.ElapsedTime);
|
||||
foreach (var guid in animation.AttachedModels)
|
||||
{
|
||||
if (!Options.TryGetModel(guid, out var m) || m is not SkeletalModel skeletalModel) continue;
|
||||
skeletalModel.Skeleton.UpdateAnimationMatrices(animation, AnimateWithRotationOnly);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
foreach (var model in Options.Models.Values)
|
||||
{
|
||||
model.Update(Options);
|
||||
}
|
||||
if (IsSkeletonTreeOpen && Options.TryGetModel(out var selected) && selected is SkeletalModel { IsVisible: true } skeletalModel)
|
||||
{
|
||||
skeletalModel.Skeleton.UpdateVertices();
|
||||
}
|
||||
}
|
||||
|
||||
CameraOp.Modify(wnd.KeyboardState, deltaSeconds);
|
||||
|
||||
if (wnd.KeyboardState.IsKeyPressed(Keys.Z) &&
|
||||
Options.TryGetModel(out var selectedModel) &&
|
||||
selectedModel is SkeletalModel)
|
||||
{
|
||||
Options.RemoveAnimations();
|
||||
Options.AnimateMesh(true);
|
||||
wnd.WindowShouldClose(true, false);
|
||||
}
|
||||
if (wnd.KeyboardState.IsKeyPressed(Keys.Space))
|
||||
Options.Tracker.IsPaused = !Options.Tracker.IsPaused;
|
||||
if (wnd.KeyboardState.IsKeyPressed(Keys.Delete))
|
||||
Options.RemoveModel(Options.SelectedModel);
|
||||
if (wnd.KeyboardState.IsKeyPressed(Keys.H))
|
||||
wnd.WindowShouldClose(true, false);
|
||||
if (wnd.KeyboardState.IsKeyPressed(Keys.Escape))
|
||||
wnd.WindowShouldClose(true, true);
|
||||
}
|
||||
|
||||
private void LoadStaticMesh(UStaticMesh original, ENaniteMeshFormat naniteFormat = ENaniteMeshFormat.NoNanite)
|
||||
{
|
||||
var guid = original.LightingGuid;
|
||||
if (Options.TryGetModel(guid, out var model))
|
||||
{
|
||||
model.AddInstance(Transform.Identity);
|
||||
Application.Current.Dispatcher.Invoke(() => model.SetupInstances());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!original.TryConvert(out var mesh, EMeshQuality.Highest, naniteFormat))
|
||||
return;
|
||||
|
||||
Options.Models[guid] = new StaticModel(original, mesh);
|
||||
Options.SelectModel(guid);
|
||||
}
|
||||
|
||||
private void LoadSkeletalMesh(USkeletalMesh original)
|
||||
{
|
||||
var guid = new FGuid((uint) original.GetFullName().GetHashCode());
|
||||
if (Options.Models.ContainsKey(guid) || !original.TryConvert(out var mesh, EMeshQuality.Highest)) return;
|
||||
|
||||
var skeletalModel = new SkeletalModel(original, mesh);
|
||||
Options.Models[guid] = skeletalModel;
|
||||
Options.SelectModel(guid);
|
||||
}
|
||||
|
||||
private void LoadSkeleton(USkeleton original)
|
||||
{
|
||||
var guid = original.Guid;
|
||||
if (Options.Models.ContainsKey(guid) || !original.TryConvert(out _, out var box)) return;
|
||||
|
||||
var fakeSkeletalModel = new SkeletalModel(original, box);
|
||||
Options.Models[guid] = fakeSkeletalModel;
|
||||
Options.SelectModel(guid);
|
||||
IsSkeletonTreeOpen = true;
|
||||
}
|
||||
|
||||
private void LoadMaterialInstance(UMaterialInstance original)
|
||||
{
|
||||
if (!Utils.TryLoadObject("Engine/Content/BasicShapes/Cube.Cube", out UStaticMesh editorCube))
|
||||
return;
|
||||
|
||||
var guid = editorCube.LightingGuid;
|
||||
if (Options.TryGetModel(guid, out var model))
|
||||
{
|
||||
model.Materials[0].SwapMaterial(original);
|
||||
Application.Current.Dispatcher.Invoke(() => model.Materials[0].Setup(Options, model.UvCount));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!editorCube.TryConvert(out var mesh, EMeshQuality.Highest))
|
||||
return;
|
||||
|
||||
Options.Models[guid] = new StaticModel(original, mesh);
|
||||
Options.SelectModel(guid);
|
||||
}
|
||||
|
||||
private void LoadPaperSprite(UPaperSprite original)
|
||||
{
|
||||
if (!(original.BakedSourceTexture?.TryLoad(out UTexture2D texture) ?? false))
|
||||
return;
|
||||
|
||||
var guid = texture.LightingGuid;
|
||||
if (Options.TryGetModel(guid, out var model))
|
||||
{
|
||||
model.AddInstance(Transform.Identity);
|
||||
Application.Current.Dispatcher.Invoke(() => model.SetupInstances());
|
||||
return;
|
||||
}
|
||||
|
||||
Options.Models[guid] = new StaticModel(original, texture);
|
||||
Options.SelectModel(guid);
|
||||
}
|
||||
|
||||
private void SetupCamera()
|
||||
{
|
||||
if (Options.TryGetModel(out var model))
|
||||
CameraOp.Setup(model.Box);
|
||||
}
|
||||
|
||||
private void LoadWorld(CancellationToken cancellationToken, UWorld original, Transform transform)
|
||||
{
|
||||
CameraOp.Setup(new FBox(FVector.ZeroVector, new FVector(0, 10, 10)));
|
||||
if (original.PersistentLevel.Load<ULevel>() is not { } persistentLevel)
|
||||
return;
|
||||
|
||||
if (persistentLevel.TryGetValue(out FSoftObjectPath runtimeCell, "WorldPartitionRuntimeCell") &&
|
||||
runtimeCell.TryLoad(out UObject worldPartition))
|
||||
{
|
||||
var position = worldPartition.GetOrDefault("Position", FVector.ZeroVector) * Constants.SCALE_DOWN_RATIO;
|
||||
var box = worldPartition.GetOrDefault("ContentBounds", new FBox(FVector.ZeroVector, FVector.OneVector));
|
||||
box *= MathF.Pow(Constants.SCALE_DOWN_RATIO, 2);
|
||||
CameraOp.Teleport(new Vector3(position.X, position.Z, position.Y), box, true);
|
||||
}
|
||||
|
||||
var length = persistentLevel.Actors.Length;
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (persistentLevel.Actors[i].Load() is not { } actor || actor.ExportType is "LODActor")
|
||||
continue;
|
||||
|
||||
Services.ApplicationService.ApplicationView.Status.UpdateStatusLabel($"{original.Name} ... {i}/{length}");
|
||||
WorldCamera(actor);
|
||||
WorldLight(actor);
|
||||
WorldMesh(actor, transform);
|
||||
AdditionalWorlds(actor, transform.Matrix, cancellationToken);
|
||||
}
|
||||
Services.ApplicationService.ApplicationView.Status.UpdateStatusLabel($"{original.Name} ... {length}/{length}");
|
||||
}
|
||||
|
||||
private void LoadJunoWorld(CancellationToken cancellationToken, UBlueprintGeneratedClass original, Transform transform)
|
||||
{
|
||||
CameraOp.Setup(new FBox(FVector.ZeroVector, new FVector(0, 10, 10)));
|
||||
|
||||
var length = 0;
|
||||
FPackageIndex[] allNodes = [];
|
||||
IPropertyHolder[] records = [];
|
||||
if (original.TryGetValue(out FPackageIndex simpleConstructionScript, "SimpleConstructionScript") &&
|
||||
simpleConstructionScript.TryLoad(out var scs) && scs.TryGetValue(out allNodes, "AllNodes"))
|
||||
length = allNodes.Length;
|
||||
else if (original.TryGetValue(out FPackageIndex inheritableComponentHandler, "InheritableComponentHandler") &&
|
||||
inheritableComponentHandler.TryLoad(out var ich) && ich.TryGetValue(out records, "Records"))
|
||||
length = records.Length;
|
||||
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
IPropertyHolder actor;
|
||||
if (allNodes is {Length: > 0} && allNodes[i].TryLoad(out UObject node))
|
||||
{
|
||||
actor = node;
|
||||
}
|
||||
else if (records is {Length: > 0})
|
||||
{
|
||||
actor = records[i];
|
||||
}
|
||||
else continue;
|
||||
|
||||
Services.ApplicationService.ApplicationView.Status.UpdateStatusLabel($"{original.Name} ... {i}/{length}");
|
||||
WorldMesh(actor, transform, true);
|
||||
}
|
||||
Services.ApplicationService.ApplicationView.Status.UpdateStatusLabel($"{original.Name} ... {length}/{length}");
|
||||
|
||||
if (Options.Models.Count == 1)
|
||||
{
|
||||
var (guid, model) = Options.Models.First();
|
||||
Options.SelectModel(guid);
|
||||
CameraOp.Setup(model.Box);
|
||||
_saveCameraMode = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void WorldCamera(UObject actor)
|
||||
{
|
||||
if (actor.ExportType != "LevelBounds" || !actor.TryGetValue(out FPackageIndex boxComponent, "BoxComponent") ||
|
||||
boxComponent.Load() is not { } boxObject) return;
|
||||
|
||||
var direction = boxObject.GetOrDefault("RelativeLocation", FVector.ZeroVector) * Constants.SCALE_DOWN_RATIO;
|
||||
var position = boxObject.GetOrDefault("RelativeScale3D", FVector.OneVector) / 2f * Constants.SCALE_DOWN_RATIO;
|
||||
CameraOp.Setup(new FBox(direction, position));
|
||||
}
|
||||
|
||||
private void WorldLight(UObject actor)
|
||||
{
|
||||
if (!actor.TryGetValue(out FPackageIndex lightComponent, "LightComponent") ||
|
||||
lightComponent.Load() is not { } lightObject) return;
|
||||
|
||||
switch (actor.ExportType)
|
||||
{
|
||||
case "PointLight":
|
||||
Options.Lights.Add(new PointLight(Options.Icons["pointlight"], lightObject));
|
||||
break;
|
||||
case "SpotLight":
|
||||
Options.Lights.Add(new SpotLight(Options.Icons["spotlight"], lightObject));
|
||||
break;
|
||||
case "RectLight":
|
||||
case "SkyLight":
|
||||
case "DirectionalLight":
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void WorldMesh(IPropertyHolder actor, Transform transform, bool forceShow = false)
|
||||
{
|
||||
if (actor.TryGetValue(out FPackageIndex[] instanceComponents, "InstanceComponents"))
|
||||
{
|
||||
foreach (var component in instanceComponents)
|
||||
{
|
||||
if (!component.TryLoad(out UStaticMeshComponent staticMeshComp) ||
|
||||
!staticMeshComp.GetStaticMesh().TryLoad(out UStaticMesh m) || m.Materials.Length < 1)
|
||||
continue;
|
||||
|
||||
var relation = CalculateTransform(staticMeshComp, transform);
|
||||
if (staticMeshComp is UInstancedStaticMeshComponent { PerInstanceSMData.Length: > 0 } instancedStaticMeshComp)
|
||||
{
|
||||
|
||||
foreach (var perInstanceData in instancedStaticMeshComp.PerInstanceSMData)
|
||||
{
|
||||
ProcessMesh(actor, instancedStaticMeshComp, m, new Transform
|
||||
{
|
||||
Relation = relation.Matrix,
|
||||
Position = perInstanceData.TransformData.Translation * Constants.SCALE_DOWN_RATIO,
|
||||
Rotation = perInstanceData.TransformData.Rotation,
|
||||
Scale = perInstanceData.TransformData.Scale3D
|
||||
});
|
||||
}
|
||||
}
|
||||
else ProcessMesh(actor, staticMeshComp, m, relation);
|
||||
}
|
||||
}
|
||||
else if (actor.TryGetValue(out FPackageIndex componentTemplate, "ComponentTemplate") &&
|
||||
componentTemplate.TryLoad(out UObject compTemplate))
|
||||
{
|
||||
UGeometryCollection geometryCollection = null;
|
||||
if (!compTemplate.TryGetValue(out UStaticMesh m, "StaticMesh") &&
|
||||
compTemplate.TryGetValue(out FPackageIndex restCollection, "RestCollection") &&
|
||||
restCollection.TryLoad(out geometryCollection) && geometryCollection.RootProxyData is { ProxyMeshes.Length: > 0 } rootProxyData)
|
||||
{
|
||||
rootProxyData.ProxyMeshes[0].TryLoad(out m);
|
||||
}
|
||||
|
||||
if (m is { Materials.Length: > 0 })
|
||||
{
|
||||
OverrideJunoVertexColors(m, geometryCollection);
|
||||
ProcessMesh(actor, compTemplate, m, CalculateTransform(compTemplate, transform), forceShow);
|
||||
}
|
||||
}
|
||||
else if (actor.TryGetValue(out FPackageIndex staticMeshComponent, "StaticMeshComponent", "ComponentTemplate", "StaticMesh", "Mesh", "LightMesh", "SplineMesh") &&
|
||||
staticMeshComponent.TryLoad(out UStaticMeshComponent staticMeshComp) &&
|
||||
staticMeshComp.GetStaticMesh().TryLoad(out UStaticMesh m) && m.Materials.Length > 0)
|
||||
{
|
||||
ProcessMesh(actor, staticMeshComp, m, CalculateTransform(staticMeshComp, transform));
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessMesh(IPropertyHolder actor, UStaticMeshComponent staticMeshComp, UStaticMesh m, Transform transform)
|
||||
{
|
||||
OverrideVertexColors(staticMeshComp, m);
|
||||
ProcessMesh(actor, staticMeshComp, m, transform, false);
|
||||
}
|
||||
private void ProcessMesh(IPropertyHolder actor, UObject staticMeshComp, UStaticMesh m, Transform transform, bool forceShow)
|
||||
{
|
||||
var bSpline = staticMeshComp is USplineMeshComponent;
|
||||
var guid = m.LightingGuid;
|
||||
if (Options.TryGetModel(guid, out var model))
|
||||
{
|
||||
model.AddInstance(transform);
|
||||
if (bSpline && model is SplineModel splineModel)
|
||||
splineModel.AddComponent((USplineMeshComponent)staticMeshComp);
|
||||
}
|
||||
else if (m.TryConvert(out var mesh, EMeshQuality.Highest, UserSettings.Default.NaniteMeshExportFormat))
|
||||
{
|
||||
model = bSpline ? new SplineModel(m, mesh, (USplineMeshComponent)staticMeshComp, transform) : new StaticModel(m, mesh, transform);
|
||||
model.IsTwoSided = actor.GetOrDefault("bMirrored", staticMeshComp.GetOrDefault("bDisallowMeshPaintPerInstance", model.IsTwoSided));
|
||||
|
||||
if (actor.TryGetAllValues(out FPackageIndex[] textureData, "TextureData"))
|
||||
{
|
||||
var material = model.Materials.FirstOrDefault();
|
||||
if (material is { IsUsed: true })
|
||||
{
|
||||
for (int j = 0; j < textureData.Length; j++)
|
||||
{
|
||||
if (textureData[j]?.Load() is not { } textureDataIdx)
|
||||
continue;
|
||||
|
||||
if (textureDataIdx.TryGetValue(out FPackageIndex overrideMaterial, "OverrideMaterial") &&
|
||||
overrideMaterial.TryLoad(out var oMaterial) && oMaterial is UMaterialInterface oUnrealMaterial)
|
||||
material.SwapMaterial(oUnrealMaterial);
|
||||
|
||||
WorldTextureData(material, textureDataIdx, "Diffuse", j switch
|
||||
{
|
||||
0 => "Diffuse",
|
||||
> 0 => $"Diffuse_Texture_{j + 1}",
|
||||
_ => CMaterialParams2.FallbackDiffuse
|
||||
});
|
||||
WorldTextureData(material, textureDataIdx, "Normal", j switch
|
||||
{
|
||||
0 => "Normals",
|
||||
> 0 => $"Normals_Texture_{j + 1}",
|
||||
_ => CMaterialParams2.FallbackNormals
|
||||
});
|
||||
WorldTextureData(material, textureDataIdx, "Specular", j switch
|
||||
{
|
||||
0 => "SpecularMasks",
|
||||
> 0 => $"SpecularMasks_{j + 1}",
|
||||
_ => CMaterialParams2.FallbackNormals
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (staticMeshComp.TryGetValue(out FPackageIndex[] overrideMaterials, "OverrideMaterials"))
|
||||
{
|
||||
for (var j = 0; j < overrideMaterials.Length && j < model.Sections.Length; j++)
|
||||
{
|
||||
var matIndex = model.Sections[j].MaterialIndex;
|
||||
if (matIndex < 0 || matIndex >= model.Materials.Length || matIndex >= overrideMaterials.Length ||
|
||||
overrideMaterials[matIndex].Load() is not UMaterialInterface unrealMaterial) continue;
|
||||
|
||||
model.Materials[matIndex].SwapMaterial(unrealMaterial);
|
||||
}
|
||||
}
|
||||
|
||||
if (forceShow)
|
||||
{
|
||||
foreach (var section in model.Sections)
|
||||
{
|
||||
section.Show = true;
|
||||
}
|
||||
}
|
||||
Options.Models[guid] = model;
|
||||
}
|
||||
|
||||
if (actor.TryGetValue(out FPackageIndex treasureLight, "PointLight", "TreasureLight") &&
|
||||
treasureLight.TryLoad(out var pl1) && pl1.Template.TryLoad(out var pl2))
|
||||
{
|
||||
Options.Lights.Add(new PointLight(guid, Options.Icons["pointlight"], pl1, pl2, transform));
|
||||
}
|
||||
if (actor.TryGetValue(out FPackageIndex spotLight, "SpotLight") &&
|
||||
spotLight.TryLoad(out var sl1) && sl1.Template.TryLoad(out var sl2))
|
||||
{
|
||||
Options.Lights.Add(new SpotLight(guid, Options.Icons["spotlight"], sl1, sl2, transform));
|
||||
}
|
||||
}
|
||||
|
||||
private Transform CalculateTransform(IPropertyHolder staticMeshComp, Transform relation)
|
||||
{
|
||||
if (staticMeshComp.TryGetValue(out FPackageIndex ap, "AttachParent") && ap.TryLoad(out UObject component))
|
||||
{
|
||||
relation = CalculateTransform(component, relation);
|
||||
}
|
||||
|
||||
return new Transform
|
||||
{
|
||||
Relation = relation.Matrix,
|
||||
Position = staticMeshComp.GetOrDefault("RelativeLocation", FVector.ZeroVector) * Constants.SCALE_DOWN_RATIO,
|
||||
Rotation = staticMeshComp.GetOrDefault("RelativeRotation", FRotator.ZeroRotator).Quaternion(),
|
||||
Scale = staticMeshComp.GetOrDefault("RelativeScale3D", FVector.OneVector)
|
||||
};
|
||||
}
|
||||
|
||||
private void OverrideJunoVertexColors(UStaticMesh staticMesh, UGeometryCollection geometryCollection = null)
|
||||
{
|
||||
if (staticMesh.RenderData is not { LODs.Length: > 0 } || staticMesh.RenderData.LODs[0].ColorVertexBuffer == null)
|
||||
return;
|
||||
|
||||
var dico = new Dictionary<byte, FColor>();
|
||||
if (geometryCollection?.Materials is not { Length: > 0 })
|
||||
{
|
||||
var distinctReds = new HashSet<byte>();
|
||||
for (int i = 0; i < staticMesh.RenderData.LODs[0].ColorVertexBuffer.Data.Length; i++)
|
||||
{
|
||||
ref var vertexColor = ref staticMesh.RenderData.LODs[0].ColorVertexBuffer.Data[i];
|
||||
var indexAsByte = vertexColor.R;
|
||||
if (indexAsByte == 255) indexAsByte = vertexColor.A;
|
||||
distinctReds.Add(indexAsByte);
|
||||
}
|
||||
|
||||
foreach (var indexAsByte in distinctReds)
|
||||
{
|
||||
var path = string.Concat("/JunoAtomAssets/Materials/MI_LegoStandard_", indexAsByte, ".MI_LegoStandard_", indexAsByte);
|
||||
if (!Utils.TryLoadObject(path, out UMaterialInterface unrealMaterial))
|
||||
continue;
|
||||
|
||||
var parameters = new CMaterialParams2();
|
||||
unrealMaterial.GetParams(parameters, EMaterialDepth.TopLayerOnly);
|
||||
|
||||
if (!parameters.TryGetLinearColor(out var color, "Color"))
|
||||
color = FLinearColor.Gray;
|
||||
|
||||
dico[indexAsByte] = color.ToFColor(true);
|
||||
}
|
||||
}
|
||||
else foreach (var material in geometryCollection.Materials)
|
||||
{
|
||||
if (!material.TryLoad(out UMaterialInterface unrealMaterial)) continue;
|
||||
|
||||
var parameters = new CMaterialParams2();
|
||||
unrealMaterial.GetParams(parameters, EMaterialDepth.TopLayerOnly);
|
||||
|
||||
if (!byte.TryParse(material.Name.SubstringAfterLast("_"), out var indexAsByte))
|
||||
indexAsByte = byte.MaxValue;
|
||||
if (!parameters.TryGetLinearColor(out var color, "Color"))
|
||||
color = FLinearColor.Gray;
|
||||
|
||||
dico[indexAsByte] = color.ToFColor(true);
|
||||
}
|
||||
|
||||
for (int i = 0; i < staticMesh.RenderData.LODs[0].ColorVertexBuffer.Data.Length; i++)
|
||||
{
|
||||
ref var vertexColor = ref staticMesh.RenderData.LODs[0].ColorVertexBuffer.Data[i];
|
||||
vertexColor = dico.TryGetValue(vertexColor.R, out var color) ? color : FColor.Gray;
|
||||
}
|
||||
}
|
||||
|
||||
private void OverrideVertexColors(UStaticMeshComponent staticMeshComp, UStaticMesh staticMesh)
|
||||
{
|
||||
if (staticMeshComp.LODData is not { Length: > 0 } || staticMesh.RenderData is not { LODs.Length: > 0 })
|
||||
return;
|
||||
|
||||
for (var lod = 0; lod < staticMeshComp.LODData.Length; lod++)
|
||||
{
|
||||
var vertexColors = staticMeshComp.LODData[lod].OverrideVertexColors;
|
||||
if (vertexColors == null) continue;
|
||||
|
||||
staticMesh.RenderData.LODs[lod].ColorVertexBuffer = vertexColors;
|
||||
}
|
||||
}
|
||||
|
||||
private void WorldTextureData(Material material, UObject textureData, string name, string key)
|
||||
{
|
||||
if (textureData.TryGetValue(out FPackageIndex package, name) && package.Load() is UTexture2D texture)
|
||||
material.Parameters.Textures[key] = texture;
|
||||
}
|
||||
|
||||
private void AdditionalWorlds(UObject actor, Matrix4x4 relation, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!actor.TryGetValue(out FSoftObjectPath[] additionalWorlds, "AdditionalWorlds") ||
|
||||
!actor.TryGetValue(out FPackageIndex staticMeshComponent, "StaticMeshComponent", "Mesh") ||
|
||||
staticMeshComponent.Load() is not { } staticMeshComp)
|
||||
return;
|
||||
|
||||
var transform = new Transform
|
||||
{
|
||||
Relation = relation,
|
||||
Position = staticMeshComp.GetOrDefault("RelativeLocation", FVector.ZeroVector) * Constants.SCALE_DOWN_RATIO,
|
||||
Rotation = staticMeshComp.GetOrDefault("RelativeRotation", FRotator.ZeroRotator).Quaternion()
|
||||
};
|
||||
|
||||
for (int j = 0; j < additionalWorlds.Length; j++)
|
||||
if (Utils.TryLoadObject(additionalWorlds[j].AssetPathName.Text, out UWorld w))
|
||||
LoadWorld(cancellationToken, w, transform);
|
||||
}
|
||||
|
||||
public void WindowResized(int width, int height)
|
||||
{
|
||||
CameraOp.AspectRatio = width / (float) height;
|
||||
Picking.WindowResized(width, height);
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
Options.ResetModelsLightsAnimations();
|
||||
Options.SelectModel(Guid.Empty);
|
||||
Options.SwapMaterial(false);
|
||||
Options.AnimateMesh(false);
|
||||
|
||||
if (_saveCameraMode) UserSettings.Default.CameraMode = CameraOp.Mode;
|
||||
UserSettings.Default.ShowSkybox = ShowSkybox;
|
||||
UserSettings.Default.ShowGrid = ShowGrid;
|
||||
UserSettings.Default.AnimateWithRotationOnly = AnimateWithRotationOnly;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_skybox?.Dispose();
|
||||
_grid?.Dispose();
|
||||
_shader?.Dispose();
|
||||
_outline?.Dispose();
|
||||
_light?.Dispose();
|
||||
_bone?.Dispose();
|
||||
_collision?.Dispose();
|
||||
Picking?.Dispose();
|
||||
Options?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,413 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using FModel.Extensions;
|
||||
using FModel.Settings;
|
||||
using FModel.Views.Snooper.Models;
|
||||
using ImGuiNET;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
|
||||
namespace FModel.Views.Snooper.Shading;
|
||||
|
||||
public class Material : IDisposable
|
||||
{
|
||||
private int _handle;
|
||||
|
||||
public readonly CMaterialParams2 Parameters;
|
||||
public string Name;
|
||||
public string Path;
|
||||
public int SelectedChannel;
|
||||
public int SelectedTexture;
|
||||
public bool IsUsed;
|
||||
|
||||
public Texture[] Diffuse;
|
||||
public Texture[] Normals;
|
||||
public Texture[] SpecularMasks;
|
||||
public Texture[] Emissive;
|
||||
|
||||
public Vector4[] DiffuseColor;
|
||||
public Vector4[] EmissiveColor;
|
||||
public Vector4 EmissiveRegion;
|
||||
|
||||
public AoParams Ao;
|
||||
public bool HasAo;
|
||||
|
||||
public float RoughnessMin = 0f;
|
||||
public float RoughnessMax = 1f;
|
||||
public float EmissiveMult = 1f;
|
||||
|
||||
public Material()
|
||||
{
|
||||
Parameters = new CMaterialParams2();
|
||||
Name = "";
|
||||
Path = "None";
|
||||
IsUsed = false;
|
||||
|
||||
Diffuse = Array.Empty<Texture>();
|
||||
Normals = Array.Empty<Texture>();
|
||||
SpecularMasks = Array.Empty<Texture>();
|
||||
Emissive = Array.Empty<Texture>();
|
||||
|
||||
DiffuseColor = Array.Empty<Vector4>();
|
||||
EmissiveColor = Array.Empty<Vector4>();
|
||||
EmissiveRegion = new Vector4(0, 0, 1, 1);
|
||||
}
|
||||
|
||||
public Material(UMaterialInterface unrealMaterial) : this()
|
||||
{
|
||||
SwapMaterial(unrealMaterial);
|
||||
}
|
||||
|
||||
public void SwapMaterial(UMaterialInterface unrealMaterial)
|
||||
{
|
||||
Name = unrealMaterial.Name;
|
||||
Path = unrealMaterial.GetPathName();
|
||||
unrealMaterial.GetParams(Parameters, UserSettings.Default.MaterialExportFormat);
|
||||
}
|
||||
|
||||
public void Setup(Options options, int uvCount)
|
||||
{
|
||||
_handle = GL.CreateProgram();
|
||||
|
||||
if (uvCount < 1 || Parameters.IsNull)
|
||||
{
|
||||
Diffuse = [new Texture(FLinearColor.Gray)];
|
||||
Normals = [new Texture(new FLinearColor(0.5f, 0.5f, 1f, 1f))];
|
||||
SpecularMasks = [new Texture(new FLinearColor(1f, 0.5f, 0.5f, 1f))];
|
||||
Emissive = new Texture[1];
|
||||
DiffuseColor = FillColors(1, Diffuse, CMaterialParams2.DiffuseColors, Vector4.One);
|
||||
EmissiveColor = [Vector4.One];
|
||||
}
|
||||
else
|
||||
{
|
||||
{ // textures
|
||||
Diffuse = FillTextures(options, uvCount, Parameters.HasTopDiffuse, CMaterialParams2.Diffuse, CMaterialParams2.FallbackDiffuse, true);
|
||||
Normals = FillTextures(options, uvCount, Parameters.HasTopNormals, CMaterialParams2.Normals, CMaterialParams2.FallbackNormals);
|
||||
SpecularMasks = FillTextures(options, uvCount, Parameters.HasTopSpecularMasks, CMaterialParams2.SpecularMasks, CMaterialParams2.FallbackSpecularMasks);
|
||||
Emissive = FillTextures(options, uvCount, true, CMaterialParams2.Emissive, CMaterialParams2.FallbackEmissive);
|
||||
}
|
||||
|
||||
{ // colors
|
||||
DiffuseColor = FillColors(uvCount, Diffuse, CMaterialParams2.DiffuseColors, Vector4.One);
|
||||
EmissiveColor = FillColors(uvCount, Emissive, CMaterialParams2.EmissiveColors, Vector4.One);
|
||||
}
|
||||
|
||||
{ // ambient occlusion + color boost
|
||||
if (Parameters.TryGetTexture2d(out var original, "M", "AEM", "AO") &&
|
||||
!original.Name.Equals("T_BlackMask") && options.TryGetTexture(original, false, out var transformed))
|
||||
{
|
||||
HasAo = true;
|
||||
Ao = new AoParams { Texture = transformed };
|
||||
if (Parameters.TryGetLinearColor(out var l, "Skin Boost Color And Exponent"))
|
||||
{
|
||||
Ao.HasColorBoost = true;
|
||||
Ao.ColorBoost = new Boost { Color = new Vector3(l.R, l.G, l.B), Exponent = l.A };
|
||||
}
|
||||
}
|
||||
|
||||
if (Parameters.TryGetScalar(out var roughnessMin, "RoughnessMin", "SpecRoughnessMin"))
|
||||
RoughnessMin = roughnessMin;
|
||||
if (Parameters.TryGetScalar(out var roughnessMax, "RoughnessMax", "SpecRoughnessMax"))
|
||||
RoughnessMax = roughnessMax;
|
||||
if (Parameters.TryGetScalar(out var roughness, "Rough", "Roughness", "Ro Multiplier", "RO_mul", "Roughness_Mult"))
|
||||
{
|
||||
var d = roughness / 2;
|
||||
RoughnessMin = roughness - d;
|
||||
RoughnessMax = roughness + d;
|
||||
}
|
||||
|
||||
if (!options.SkipEmissive())
|
||||
{
|
||||
if (Parameters.TryGetScalar(out var emissiveMultScalar, "emissive mult", "Emissive_Mult", "EmissiveIntensity", "EmissionIntensity"))
|
||||
EmissiveMult = emissiveMultScalar;
|
||||
else if (Parameters.TryGetLinearColor(out var emissiveMultColor, "Emissive Multiplier", "EmissiveMultiplier"))
|
||||
EmissiveMult = emissiveMultColor.R;
|
||||
}
|
||||
else
|
||||
EmissiveMult = 0f;
|
||||
|
||||
if (Parameters.TryGetLinearColor(out var EmissiveUVs,
|
||||
"EmissiveUVs_RG_UpperLeftCorner_BA_LowerRightCorner",
|
||||
"Emissive Texture UVs RG_TopLeft BA_BottomRight",
|
||||
"Emissive 2 UV Positioning (RG)UpperLeft (BA)LowerRight",
|
||||
"EmissiveUVPositioning (RG)UpperLeft (BA)LowerRight",
|
||||
"Emissive_CH", "EmissiveColor4LM", "Emissive Sphere Center"))
|
||||
EmissiveRegion = new Vector4(EmissiveUVs.R, EmissiveUVs.G, EmissiveUVs.B, EmissiveUVs.A);
|
||||
|
||||
if ((Parameters.TryGetSwitch(out var swizzleRoughnessToGreen, "SwizzleRoughnessToGreen") && swizzleRoughnessToGreen) ||
|
||||
Parameters.Textures.ContainsKey("SRM"))
|
||||
{
|
||||
foreach (var specMask in SpecularMasks)
|
||||
{
|
||||
specMask.SwizzleMask = new []
|
||||
{
|
||||
(int) PixelFormat.Red,
|
||||
(int) PixelFormat.Blue,
|
||||
(int) PixelFormat.Green,
|
||||
(int) PixelFormat.Alpha
|
||||
};
|
||||
specMask.Swizzle();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <param name="options">just the cache object</param>
|
||||
/// <param name="uvCount">number of item in the array</param>
|
||||
/// <param name="top">has at least 1 clearly defined texture, else will go straight to fallback</param>
|
||||
/// <param name="triggers">list of texture parameter names by uv channel</param>
|
||||
/// <param name="fallback">fallback texture name to use if no top texture found</param>
|
||||
/// <param name="first">if no top texture, no fallback texture, then use the first texture found</param>
|
||||
private Texture[] FillTextures(Options options, int uvCount, bool top, string[][] triggers, string fallback, bool first = false)
|
||||
{
|
||||
UTexture original;
|
||||
Texture transformed;
|
||||
var fix = fallback == CMaterialParams2.FallbackSpecularMasks;
|
||||
var textures = new Texture[uvCount];
|
||||
|
||||
if (top)
|
||||
{
|
||||
for (int i = 0; i < textures.Length; i++)
|
||||
{
|
||||
if (Parameters.TryGetTexture2d(out original, triggers[i]) && options.TryGetTexture(original, fix, out transformed))
|
||||
textures[i] = transformed;
|
||||
else if (i > 0 && textures[i - 1] != null)
|
||||
textures[i] = textures[i - 1];
|
||||
}
|
||||
}
|
||||
else if (Parameters.TryGetTexture2d(out original, fallback) && options.TryGetTexture(original, fix, out transformed))
|
||||
{
|
||||
for (int i = 0; i < textures.Length; i++)
|
||||
textures[i] = transformed;
|
||||
}
|
||||
else if (first && Parameters.TryGetFirstTexture2d(out original) && options.TryGetTexture(original, fix, out transformed))
|
||||
{
|
||||
for (int i = 0; i < textures.Length; i++)
|
||||
textures[i] = transformed;
|
||||
}
|
||||
return textures;
|
||||
}
|
||||
|
||||
/// <param name="uvCount">number of item in the array</param>
|
||||
/// <param name="textures">reference array</param>
|
||||
/// <param name="triggers">list of color parameter names by uv channel</param>
|
||||
/// <param name="fallback">fallback color to use if no trigger was found</param>
|
||||
private Vector4[] FillColors(int uvCount, Texture[] textures, string[][] triggers, Vector4 fallback)
|
||||
{
|
||||
var colors = new Vector4[uvCount];
|
||||
for (int i = 0; i < colors.Length; i++)
|
||||
{
|
||||
if (textures[i] == null) continue;
|
||||
|
||||
if (Parameters.TryGetLinearColor(out var color, triggers[i]) && color is { A: > 0 })
|
||||
{
|
||||
colors[i] = new Vector4(color.R, color.G, color.B, color.A);
|
||||
}
|
||||
else colors[i] = fallback;
|
||||
}
|
||||
return colors;
|
||||
}
|
||||
|
||||
public void Render(Shader shader)
|
||||
{
|
||||
var unit = 0;
|
||||
for (var i = 0; i < Diffuse.Length; i++)
|
||||
{
|
||||
shader.SetUniform($"uParameters.Diffuse[{i}].Sampler", unit);
|
||||
shader.SetUniform($"uParameters.Diffuse[{i}].Color", DiffuseColor[i]);
|
||||
Diffuse[i]?.Bind(TextureUnit.Texture0 + unit++);
|
||||
}
|
||||
|
||||
for (var i = 0; i < Normals.Length; i++)
|
||||
{
|
||||
shader.SetUniform($"uParameters.Normals[{i}].Sampler", unit);
|
||||
Normals[i]?.Bind(TextureUnit.Texture0 + unit++);
|
||||
}
|
||||
|
||||
for (var i = 0; i < SpecularMasks.Length; i++)
|
||||
{
|
||||
shader.SetUniform($"uParameters.SpecularMasks[{i}].Sampler", unit);
|
||||
SpecularMasks[i]?.Bind(TextureUnit.Texture0 + unit++);
|
||||
}
|
||||
|
||||
for (var i = 0; i < Emissive.Length; i++)
|
||||
{
|
||||
shader.SetUniform($"uParameters.Emissive[{i}].Sampler", unit);
|
||||
shader.SetUniform($"uParameters.Emissive[{i}].Color", EmissiveColor[i]);
|
||||
Emissive[i]?.Bind(TextureUnit.Texture0 + unit++);
|
||||
}
|
||||
|
||||
Ao.Texture?.Bind(TextureUnit.Texture31);
|
||||
shader.SetUniform("uParameters.Ao.Sampler", 31);
|
||||
shader.SetUniform("uParameters.Ao.HasColorBoost", Ao.HasColorBoost);
|
||||
shader.SetUniform("uParameters.Ao.ColorBoost.Color", Ao.ColorBoost.Color);
|
||||
shader.SetUniform("uParameters.Ao.ColorBoost.Exponent", Ao.ColorBoost.Exponent);
|
||||
shader.SetUniform("uParameters.HasAo", HasAo);
|
||||
|
||||
shader.SetUniform("uParameters.EmissiveRegion", EmissiveRegion);
|
||||
shader.SetUniform("uParameters.RoughnessMin", RoughnessMin);
|
||||
shader.SetUniform("uParameters.RoughnessMax", RoughnessMax);
|
||||
shader.SetUniform("uParameters.EmissiveMult", EmissiveMult);
|
||||
}
|
||||
|
||||
private const string _mult = "x %.2f";
|
||||
private const float _step = 0.01f;
|
||||
private const float _zero = 0.000001f; // doesn't actually work if _infinite is used as max value /shrug
|
||||
private const float _infinite = 0.0f;
|
||||
private const ImGuiSliderFlags _clamp = ImGuiSliderFlags.AlwaysClamp;
|
||||
public void ImGuiParameters()
|
||||
{
|
||||
if (ImGui.BeginTable("parameters", 2))
|
||||
{
|
||||
var id = 1;
|
||||
SnimGui.Layout("Roughness Min");ImGui.PushID(id++);
|
||||
ImGui.DragFloat("", ref RoughnessMin, _step, _zero, 1.0f, _mult, _clamp);
|
||||
ImGui.PopID();SnimGui.Layout("Roughness Max");ImGui.PushID(id++);
|
||||
ImGui.DragFloat("", ref RoughnessMax, _step, _zero, 1.0f, _mult, _clamp);
|
||||
ImGui.PopID();SnimGui.Layout("Emissive Multiplier");ImGui.PushID(id++);
|
||||
ImGui.DragFloat("", ref EmissiveMult, _step, _zero, _infinite, _mult, _clamp);
|
||||
ImGui.PopID();
|
||||
|
||||
if (HasAo && Ao.HasColorBoost)
|
||||
{
|
||||
SnimGui.Layout("Color Boost");ImGui.PushID(id++);
|
||||
ImGui.ColorEdit3("", ref Ao.ColorBoost.Color);ImGui.PopID();
|
||||
SnimGui.Layout("Color Boost Exponent");ImGui.PushID(id++);
|
||||
ImGui.DragFloat("", ref Ao.ColorBoost.Exponent, _step, _zero, _infinite, _mult, _clamp);
|
||||
ImGui.PopID();
|
||||
}
|
||||
ImGui.EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
public void ImGuiBaseProperties(string id)
|
||||
{
|
||||
if (ImGui.BeginTable(id, 2, ImGuiTableFlags.SizingStretchProp))
|
||||
{
|
||||
Layout("Blend", Parameters.BlendMode.GetDescription(), true, true);
|
||||
Layout("Shading", Parameters.ShadingModel.GetDescription(), true, true);
|
||||
ImGui.EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
public void ImGuiDictionaries<T>(string id, Dictionary<string, T> dictionary, bool center = false, bool wrap = false)
|
||||
{
|
||||
if (ImGui.BeginTable(id, 2))
|
||||
{
|
||||
foreach ((string key, T value) in dictionary.Reverse())
|
||||
{
|
||||
Layout(key, value, center, wrap);
|
||||
}
|
||||
ImGui.EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
public void ImGuiColors(Dictionary<string, FLinearColor> colors)
|
||||
{
|
||||
foreach ((string key, FLinearColor value) in colors.Reverse())
|
||||
{
|
||||
ImGui.ColorButton(key, new Vector4(value.R, value.G, value.B, value.A), ImGuiColorEditFlags.None, new Vector2(16));
|
||||
ImGui.SameLine();ImGui.Text(key);SnimGui.TooltipCopy(key);
|
||||
}
|
||||
}
|
||||
|
||||
public bool ImGuiTextures(Dictionary<string, Texture> icons, IRenderableModel model)
|
||||
{
|
||||
if (ImGui.BeginTable("material_textures", 2))
|
||||
{
|
||||
SnimGui.Layout("Channel");ImGui.PushID(1); ImGui.BeginDisabled(model.UvCount < 2);
|
||||
ImGui.DragInt("", ref SelectedChannel, _step, 0, model.UvCount - 1, "UV %i", ImGuiSliderFlags.AlwaysClamp);
|
||||
ImGui.EndDisabled();ImGui.PopID();SnimGui.Layout("Type");ImGui.PushID(2);
|
||||
ImGui.Combo("texture_type", ref SelectedTexture, "Diffuse\0Normals\0Specular\0Ambient Occlusion\0Emissive\0");
|
||||
ImGui.PopID();
|
||||
|
||||
switch (SelectedTexture)
|
||||
{
|
||||
case 0 when DiffuseColor.Length > 0:
|
||||
SnimGui.Layout("Color");ImGui.PushID(3);
|
||||
ImGui.ColorEdit4("", ref DiffuseColor[SelectedChannel], ImGuiColorEditFlags.NoAlpha);
|
||||
ImGui.PopID();
|
||||
break;
|
||||
case 4 when EmissiveColor.Length > 0:
|
||||
SnimGui.Layout("Color");ImGui.PushID(3);
|
||||
ImGui.ColorEdit4("", ref EmissiveColor[SelectedChannel], ImGuiColorEditFlags.NoAlpha);
|
||||
ImGui.PopID();SnimGui.Layout("Region");ImGui.PushID(4);
|
||||
ImGui.DragFloat4("", ref EmissiveRegion, _step, _zero, 1.0f, "%.2f", _clamp);
|
||||
ImGui.PopID();
|
||||
break;
|
||||
}
|
||||
|
||||
ImGui.EndTable();
|
||||
}
|
||||
|
||||
var texture = GetSelectedTexture() ?? icons["noimage"];
|
||||
ImGui.Image(texture.GetPointer(),
|
||||
new Vector2(ImGui.GetContentRegionAvail().X - ImGui.GetScrollX()),
|
||||
Vector2.Zero, Vector2.One);
|
||||
return ImGui.IsItemHovered() && ImGui.IsMouseDoubleClicked(ImGuiMouseButton.Left);
|
||||
}
|
||||
|
||||
public Texture GetSelectedTexture()
|
||||
{
|
||||
return SelectedTexture switch
|
||||
{
|
||||
0 when Diffuse.Length > 0 => Diffuse[SelectedChannel],
|
||||
1 when Normals.Length > 0 => Normals[SelectedChannel],
|
||||
2 when SpecularMasks.Length > 0 => SpecularMasks[SelectedChannel],
|
||||
3 => Ao.Texture,
|
||||
4 when Emissive.Length > 0 => Emissive[SelectedChannel],
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
private void Layout<T>(string key, T value, bool center = false, bool wrap = false)
|
||||
{
|
||||
SnimGui.Layout(key, true);
|
||||
var text = $"{value:N}";
|
||||
if (center) ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (ImGui.GetColumnWidth() - ImGui.CalcTextSize(text).X) / 2);
|
||||
if (wrap) ImGui.TextWrapped(text); else ImGui.Text(text);
|
||||
SnimGui.TooltipCopy(text);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
for (int i = 0; i < Diffuse.Length; i++)
|
||||
{
|
||||
Diffuse[i]?.Dispose();
|
||||
}
|
||||
for (int i = 0; i < Normals.Length; i++)
|
||||
{
|
||||
Normals[i]?.Dispose();
|
||||
}
|
||||
for (int i = 0; i < SpecularMasks.Length; i++)
|
||||
{
|
||||
SpecularMasks[i]?.Dispose();
|
||||
}
|
||||
for (int i = 0; i < Emissive.Length; i++)
|
||||
{
|
||||
Emissive[i]?.Dispose();
|
||||
}
|
||||
Ao.Texture?.Dispose();
|
||||
GL.DeleteProgram(_handle);
|
||||
}
|
||||
}
|
||||
|
||||
public struct AoParams
|
||||
{
|
||||
public Texture Texture;
|
||||
|
||||
public Boost ColorBoost;
|
||||
public bool HasColorBoost;
|
||||
}
|
||||
|
||||
public struct Boost
|
||||
{
|
||||
public Vector3 Color;
|
||||
public float Exponent;
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
|
||||
namespace FModel.Views.Snooper.Shading;
|
||||
|
||||
public class Shader : IDisposable
|
||||
{
|
||||
private readonly int _handle;
|
||||
private readonly int _vHandle;
|
||||
private readonly int _fHandle;
|
||||
private readonly Dictionary<string, int> _uniformsLocation = new ();
|
||||
|
||||
public Shader() : this("default") {}
|
||||
|
||||
public Shader(string name1, string name2 = null)
|
||||
{
|
||||
_handle = GL.CreateProgram();
|
||||
_vHandle = LoadShader(ShaderType.VertexShader, $"{name1}.vert");
|
||||
_fHandle = LoadShader(ShaderType.FragmentShader, $"{name2 ?? name1}.frag");
|
||||
Attach();
|
||||
}
|
||||
|
||||
private void Attach()
|
||||
{
|
||||
GL.AttachShader(_handle, _vHandle);
|
||||
GL.AttachShader(_handle, _fHandle);
|
||||
GL.LinkProgram(_handle);
|
||||
GL.GetProgram(_handle, GetProgramParameterName.LinkStatus, out var status);
|
||||
if (status == 0)
|
||||
{
|
||||
throw new Exception($"Program failed to link with error: {GL.GetProgramInfoLog(_handle)}");
|
||||
}
|
||||
}
|
||||
|
||||
private void Detach()
|
||||
{
|
||||
GL.DetachShader(_handle, _vHandle);
|
||||
GL.DetachShader(_handle, _fHandle);
|
||||
GL.DeleteShader(_vHandle);
|
||||
GL.DeleteShader(_fHandle);
|
||||
}
|
||||
|
||||
private int LoadShader(ShaderType type, string file)
|
||||
{
|
||||
var executingAssembly = Assembly.GetExecutingAssembly();
|
||||
var executingAssemblyName = executingAssembly.GetName().Name;
|
||||
using var stream = executingAssembly.GetManifestResourceStream($"{executingAssemblyName}.Resources.{file}");
|
||||
using var reader = new StreamReader(stream);
|
||||
var handle = GL.CreateShader(type);
|
||||
|
||||
var content = reader.ReadToEnd();
|
||||
if (file.Equals("default.frag") && GL.GetInteger(GetPName.MaxTextureCoords) == 0)
|
||||
content = content.Replace("#define MAX_UV_COUNT 8", "#define MAX_UV_COUNT 1");
|
||||
if (type == ShaderType.VertexShader && Array.IndexOf(["default.vert", "outline.vert", "picking.vert"], file) > -1)
|
||||
{
|
||||
using var splineStream = executingAssembly.GetManifestResourceStream($"{executingAssemblyName}.Resources.spline.vert");
|
||||
using var splineReader = new StreamReader(splineStream);
|
||||
content = splineReader.ReadToEnd() + Environment.NewLine + content.Replace("#version 460 core", "");
|
||||
}
|
||||
|
||||
GL.ShaderSource(handle, content);
|
||||
GL.CompileShader(handle);
|
||||
string infoLog = GL.GetShaderInfoLog(handle);
|
||||
if (!string.IsNullOrWhiteSpace(infoLog))
|
||||
{
|
||||
throw new Exception($"Error compiling shader of type {type}, failed with error {infoLog}");
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
public void Use()
|
||||
{
|
||||
GL.UseProgram(_handle);
|
||||
}
|
||||
|
||||
public void Render(Matrix4x4 viewMatrix, Vector3 viewPos, Matrix4x4 projMatrix)
|
||||
{
|
||||
Render(viewMatrix, projMatrix);
|
||||
SetUniform("uViewPos", viewPos);
|
||||
}
|
||||
public void Render(Matrix4x4 viewMatrix, Matrix4x4 projMatrix)
|
||||
{
|
||||
Use();
|
||||
SetUniform("uView", viewMatrix);
|
||||
SetUniform("uProjection", projMatrix);
|
||||
}
|
||||
|
||||
public void SetUniform(string name, int value)
|
||||
{
|
||||
GL.Uniform1(GetUniformLocation(name), value);
|
||||
}
|
||||
|
||||
public unsafe void SetUniform(string name, Matrix4x4 value) => UniformMatrix4(name, (float*) &value);
|
||||
public unsafe void UniformMatrix4(string name, float* value)
|
||||
{
|
||||
GL.UniformMatrix4(GetUniformLocation(name), 1, false, value);
|
||||
}
|
||||
|
||||
public void SetUniform(string name, bool value) => SetUniform(name, Convert.ToUInt32(value));
|
||||
|
||||
public void SetUniform(string name, uint value)
|
||||
{
|
||||
GL.Uniform1(GetUniformLocation(name), value);
|
||||
}
|
||||
|
||||
public void SetUniform(string name, float value)
|
||||
{
|
||||
GL.Uniform1(GetUniformLocation(name), value);
|
||||
}
|
||||
|
||||
public void SetUniform(string name, Vector2 value) => SetUniform3(name, value.X, value.Y);
|
||||
public void SetUniform3(string name, float x, float y)
|
||||
{
|
||||
GL.Uniform2(GetUniformLocation(name), x, y);
|
||||
}
|
||||
|
||||
public void SetUniform(string name, Vector3 value) => SetUniform3(name, value.X, value.Y, value.Z);
|
||||
public void SetUniform3(string name, float x, float y, float z)
|
||||
{
|
||||
GL.Uniform3(GetUniformLocation(name), x, y, z);
|
||||
}
|
||||
|
||||
public void SetUniform(string name, Vector4 value) => SetUniform4(name, value.X, value.Y, value.Z, value.W);
|
||||
public void SetUniform4(string name, float x, float y, float z, float w)
|
||||
{
|
||||
GL.Uniform4(GetUniformLocation(name), x, y, z, w);
|
||||
}
|
||||
|
||||
private int GetUniformLocation(string name)
|
||||
{
|
||||
if (!_uniformsLocation.TryGetValue(name, out int location))
|
||||
{
|
||||
location = GL.GetUniformLocation(_handle, name);
|
||||
_uniformsLocation.Add(name, location);
|
||||
if (location == -1)
|
||||
{
|
||||
throw new Exception($"{name} uniform not found on shader.");
|
||||
}
|
||||
}
|
||||
return location;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Detach();
|
||||
GL.DeleteProgram(_handle);
|
||||
}
|
||||
}
|
||||
@@ -1,316 +0,0 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Windows;
|
||||
using CUE4Parse_Conversion.Textures;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using ImGuiNET;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace FModel.Views.Snooper.Shading;
|
||||
|
||||
public class Texture : IDisposable
|
||||
{
|
||||
private readonly int _handle;
|
||||
private readonly TextureType _type;
|
||||
private readonly TextureTarget _target;
|
||||
|
||||
public readonly string Type;
|
||||
public readonly FGuid Guid;
|
||||
public readonly string Name;
|
||||
public readonly string Path;
|
||||
public readonly EPixelFormat Format;
|
||||
public readonly uint ImportedWidth;
|
||||
public readonly uint ImportedHeight;
|
||||
public int Width;
|
||||
public int Height;
|
||||
|
||||
private const int DisabledChannel = (int)BlendingFactor.Zero;
|
||||
private readonly bool[] _values = [true, true, true, true];
|
||||
private readonly string[] _labels = ["R", "G", "B", "A"];
|
||||
public int[] SwizzleMask =
|
||||
[
|
||||
(int) PixelFormat.Red,
|
||||
(int) PixelFormat.Green,
|
||||
(int) PixelFormat.Blue,
|
||||
(int) PixelFormat.Alpha
|
||||
];
|
||||
|
||||
private Texture(TextureType type)
|
||||
{
|
||||
_handle = GL.GenTexture();
|
||||
_type = type;
|
||||
_target = _type switch
|
||||
{
|
||||
TextureType.Cubemap => TextureTarget.TextureCubeMap,
|
||||
TextureType.MsaaFramebuffer => TextureTarget.Texture2DMultisample,
|
||||
_ => TextureTarget.Texture2D
|
||||
};
|
||||
|
||||
Guid = new FGuid();
|
||||
}
|
||||
|
||||
public Texture(uint width, uint height) : this(TextureType.MsaaFramebuffer)
|
||||
{
|
||||
Width = (int) width;
|
||||
Height = (int) height;
|
||||
Bind(TextureUnit.Texture0);
|
||||
|
||||
GL.TexImage2DMultisample(TextureTargetMultisample.Texture2DMultisample, Constants.SAMPLES_COUNT, PixelInternalFormat.Rgb, Width, Height, true);
|
||||
GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, _target, _handle, 0);
|
||||
}
|
||||
|
||||
public Texture(int width, int height) : this(TextureType.Framebuffer)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
Bind(TextureUnit.Texture0);
|
||||
|
||||
GL.TexImage2D(_target, 0, PixelInternalFormat.Rgb, Width, Height, 0, PixelFormat.Rgb, PixelType.UnsignedByte, IntPtr.Zero);
|
||||
|
||||
GL.TexParameter(_target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.Linear);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureWrapS, (int) TextureWrapMode.ClampToEdge);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureWrapT, (int) TextureWrapMode.ClampToEdge);
|
||||
|
||||
GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, _target, _handle, 0);
|
||||
}
|
||||
|
||||
public Texture(SKBitmap bitmap, UTexture texture2D) : this(TextureType.Normal)
|
||||
{
|
||||
Type = texture2D.ExportType;
|
||||
Guid = texture2D.LightingGuid;
|
||||
Name = texture2D.Name;
|
||||
Path = texture2D.GetPathName();
|
||||
Format = texture2D.Format;
|
||||
Width = bitmap.Width;
|
||||
Height = bitmap.Height;
|
||||
Bind(TextureUnit.Texture0);
|
||||
|
||||
var internalFormat = bitmap.ColorType switch
|
||||
{
|
||||
SKColorType.Gray8 => PixelInternalFormat.R8,
|
||||
_ => texture2D.SRGB ? PixelInternalFormat.Srgb : PixelInternalFormat.Rgb
|
||||
};
|
||||
|
||||
var pixelFormat = bitmap.ColorType switch
|
||||
{
|
||||
SKColorType.Gray8 => PixelFormat.Red,
|
||||
SKColorType.Bgra8888 => PixelFormat.Bgra,
|
||||
_ => PixelFormat.Rgba
|
||||
};
|
||||
|
||||
GL.TexImage2D(_target, 0, internalFormat, Width, Height, 0, pixelFormat, PixelType.UnsignedByte, bitmap.Bytes);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.LinearMipmapLinear);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureBaseLevel, 0);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureMaxLevel, 8);
|
||||
|
||||
GL.GenerateMipmap(GenerateMipmapTarget.Texture2D);
|
||||
bitmap.Dispose();
|
||||
}
|
||||
|
||||
public Texture(FLinearColor color) : this(TextureType.Normal)
|
||||
{
|
||||
Type = "LinearColor";
|
||||
Name = color.Hex;
|
||||
Width = 1;
|
||||
Height = 1;
|
||||
Bind(TextureUnit.Texture0);
|
||||
|
||||
GL.TexImage2D(_target, 0, PixelInternalFormat.Rgba, Width, Height, 0, PixelFormat.Rgba, PixelType.Float, ref color);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.LinearMipmapLinear);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureBaseLevel, 0);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureMaxLevel, 8);
|
||||
|
||||
GL.GenerateMipmap(GenerateMipmapTarget.Texture2D);
|
||||
}
|
||||
|
||||
public Texture(string[] textures) : this(TextureType.Cubemap)
|
||||
{
|
||||
Bind(TextureUnit.Texture0);
|
||||
|
||||
for (int t = 0; t < textures.Length; t++)
|
||||
{
|
||||
ProcessPixels(textures[t], TextureTarget.TextureCubeMapPositiveX + t);
|
||||
}
|
||||
|
||||
GL.TexParameter(_target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.LinearMipmapLinear);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureWrapR, (int) TextureWrapMode.ClampToEdge);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureWrapS, (int) TextureWrapMode.ClampToEdge);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureWrapT, (int) TextureWrapMode.ClampToEdge);
|
||||
|
||||
GL.GenerateMipmap(GenerateMipmapTarget.TextureCubeMap);
|
||||
}
|
||||
|
||||
public Texture(string texture) : this(TextureType.Normal)
|
||||
{
|
||||
Bind(TextureUnit.Texture0);
|
||||
|
||||
ProcessPixels(texture, _target);
|
||||
|
||||
GL.TexParameter(_target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.Linear);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureWrapR, (int) TextureWrapMode.ClampToEdge);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureWrapS, (int) TextureWrapMode.ClampToEdge);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureWrapT, (int) TextureWrapMode.ClampToEdge);
|
||||
}
|
||||
|
||||
private void ProcessPixels(string texture, TextureTarget target)
|
||||
{
|
||||
var info = Application.GetResourceStream(new Uri($"/FModel;component/Resources/{texture}.png", UriKind.Relative));
|
||||
using var img = Image.Load<Rgba32>(info.Stream);
|
||||
Width = img.Width;
|
||||
Height = img.Height;
|
||||
GL.TexImage2D(target, 0, PixelInternalFormat.Rgba8, Width, Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero);
|
||||
img.ProcessPixelRows(accessor =>
|
||||
{
|
||||
for (int y = 0; y < accessor.Height; y++)
|
||||
{
|
||||
GL.TexSubImage2D(target, 0, 0, y, accessor.Width, 1, PixelFormat.Rgba, PixelType.UnsignedByte, accessor.GetRowSpan(y).ToArray());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void Bind(TextureUnit textureSlot)
|
||||
{
|
||||
GL.ActiveTexture(textureSlot);
|
||||
Bind(_target);
|
||||
}
|
||||
|
||||
public void Bind(TextureTarget target)
|
||||
{
|
||||
GL.BindTexture(target, _handle);
|
||||
}
|
||||
|
||||
public void Bind()
|
||||
{
|
||||
GL.BindTexture(_target, _handle);
|
||||
}
|
||||
|
||||
public void Swizzle()
|
||||
{
|
||||
Bind();
|
||||
GL.TexParameter(_target, TextureParameterName.TextureSwizzleRgba, SwizzleMask);
|
||||
}
|
||||
|
||||
public IntPtr GetPointer() => (IntPtr) _handle;
|
||||
|
||||
public void WindowResized(int width, int height)
|
||||
{
|
||||
Width = width;
|
||||
Height = height;
|
||||
|
||||
Bind();
|
||||
switch (_type)
|
||||
{
|
||||
case TextureType.MsaaFramebuffer:
|
||||
GL.TexImage2DMultisample(TextureTargetMultisample.Texture2DMultisample, Constants.SAMPLES_COUNT, PixelInternalFormat.Rgb, Width, Height, true);
|
||||
GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, _target, _handle, 0);
|
||||
break;
|
||||
case TextureType.Framebuffer:
|
||||
GL.TexImage2D(_target, 0, PixelInternalFormat.Rgb, Width, Height, 0, PixelFormat.Rgb, PixelType.UnsignedByte, IntPtr.Zero);
|
||||
GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, _target, _handle, 0);
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GL.DeleteTexture(_handle);
|
||||
}
|
||||
|
||||
private Vector3 _scrolling = new (0.0f, 0.0f, 1.0f);
|
||||
public void ImGuiTextureInspector()
|
||||
{
|
||||
if (ImGui.BeginTable("texture_inspector", 2, ImGuiTableFlags.SizingStretchProp))
|
||||
{
|
||||
SnimGui.NoFramePaddingOnY(() =>
|
||||
{
|
||||
SnimGui.Layout("Type");ImGui.Text($" : ({Format}) {Name}");
|
||||
SnimGui.TooltipCopy("(?) Click to Copy Path", Path);
|
||||
SnimGui.Layout("Guid");ImGui.Text($" : {Guid.ToString(EGuidFormats.UniqueObjectGuid)}");
|
||||
SnimGui.Layout("Size");
|
||||
ImGui.Text($" : {Width}x{Height}");
|
||||
|
||||
SnimGui.Layout("Swizzle");
|
||||
for (int c = 0; c < SwizzleMask.Length; c++)
|
||||
{
|
||||
if (ImGui.Checkbox(_labels[c], ref _values[c]))
|
||||
{
|
||||
Bind();
|
||||
GL.TexParameter(_target, TextureParameterName.TextureSwizzleR + c, _values[c] ? SwizzleMask[c] : DisabledChannel);
|
||||
}
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
ImGui.EndTable();
|
||||
});
|
||||
}
|
||||
|
||||
var io = ImGui.GetIO();
|
||||
var canvasP0 = ImGui.GetCursorScreenPos();
|
||||
var canvasSize = ImGui.GetContentRegionAvail();
|
||||
if (canvasSize.X < 50.0f) canvasSize.X = 50.0f;
|
||||
if (canvasSize.Y < 50.0f) canvasSize.Y = 50.0f;
|
||||
var canvasP1 = canvasP0 + canvasSize;
|
||||
var origin = new Vector2(canvasP0.X + _scrolling.X, canvasP0.Y + _scrolling.Y);
|
||||
var absoluteMiddle = canvasSize / 2.0f;
|
||||
|
||||
ImGui.InvisibleButton("texture_inspector_canvas", canvasSize, ImGuiButtonFlags.MouseButtonLeft);
|
||||
if (ImGui.IsItemActive() && ImGui.IsMouseDragging(ImGuiMouseButton.Left))
|
||||
{
|
||||
_scrolling.X += io.MouseDelta.X;
|
||||
_scrolling.Y += io.MouseDelta.Y;
|
||||
}
|
||||
else if (ImGui.IsItemHovered() && io.MouseWheel != 0.0f)
|
||||
{
|
||||
var zoomFactor = 1.0f + io.MouseWheel * 0.1f;
|
||||
var mousePosCanvas = io.MousePos - origin;
|
||||
|
||||
_scrolling.X -= (mousePosCanvas.X - absoluteMiddle.X) * (zoomFactor - 1);
|
||||
_scrolling.Y -= (mousePosCanvas.Y - absoluteMiddle.Y) * (zoomFactor - 1);
|
||||
_scrolling.Z *= zoomFactor;
|
||||
origin = new Vector2(canvasP0.X + _scrolling.X, canvasP0.Y + _scrolling.Y);
|
||||
}
|
||||
|
||||
var drawList = ImGui.GetWindowDrawList();
|
||||
drawList.AddRectFilled(canvasP0, canvasP1, 0xFF242424);
|
||||
drawList.PushClipRect(canvasP0, canvasP1, true);
|
||||
{
|
||||
var sensitivity = _scrolling.Z * 25.0f;
|
||||
for (float x = _scrolling.X % sensitivity; x < canvasSize.X; x += sensitivity)
|
||||
drawList.AddLine(canvasP0 with { X = canvasP0.X + x }, canvasP1 with { X = canvasP0.X + x }, 0x28C8C8C8);
|
||||
for (float y = _scrolling.Y % sensitivity; y < canvasSize.Y; y += sensitivity)
|
||||
drawList.AddLine(canvasP0 with { Y = canvasP0.Y + y }, canvasP1 with { Y = canvasP0.Y + y }, 0x28C8C8C8);
|
||||
}
|
||||
drawList.PopClipRect();
|
||||
|
||||
drawList.PushClipRect(canvasP0, canvasP1, true);
|
||||
{
|
||||
var relativeMiddle = origin + absoluteMiddle;
|
||||
var ratio = Math.Min(canvasSize.X / Width, canvasSize.Y / Height) * 0.95f * _scrolling.Z;
|
||||
var size = new Vector2(Width, Height) * ratio / 2f;
|
||||
|
||||
drawList.AddImage(GetPointer(), relativeMiddle - size, relativeMiddle + size);
|
||||
drawList.AddRect(relativeMiddle - size, relativeMiddle + size, 0xFFFFFFFF);
|
||||
}
|
||||
drawList.PopClipRect();
|
||||
}
|
||||
}
|
||||
|
||||
public enum TextureType
|
||||
{
|
||||
Normal,
|
||||
Cubemap,
|
||||
Framebuffer,
|
||||
MsaaFramebuffer
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
|
||||
namespace FModel.Views.Snooper.Shading;
|
||||
|
||||
public static class TextureHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Red : Specular (not used anymore)
|
||||
/// Green : Metallic
|
||||
/// Blue : Roughness
|
||||
/// </summary>
|
||||
public static void FixChannels(string game, Texture texture)
|
||||
{
|
||||
switch (game)
|
||||
{
|
||||
// R: Whatever (AO / S / E / ...)
|
||||
// G: Roughness
|
||||
// B: Metallic
|
||||
case "GAMEFACE":
|
||||
case "HK_PROJECT":
|
||||
case "COSMICSHAKE":
|
||||
case "PHOENIX":
|
||||
case "ATOMICHEART":
|
||||
case "MULTIVERSUS":
|
||||
case "BODYCAM":
|
||||
case "TSLGAME":
|
||||
{
|
||||
texture.SwizzleMask =
|
||||
[
|
||||
(int) PixelFormat.Red,
|
||||
(int) PixelFormat.Blue,
|
||||
(int) PixelFormat.Green,
|
||||
(int) PixelFormat.Alpha
|
||||
];
|
||||
break;
|
||||
}
|
||||
// R: Metallic
|
||||
// G: Roughness
|
||||
// B: Whatever (AO / S / E / ...)
|
||||
case "SHOOTERGAME":
|
||||
case "DIVINEKNOCKOUT":
|
||||
case "MOONMAN":
|
||||
case "SHF":
|
||||
{
|
||||
texture.SwizzleMask =
|
||||
[
|
||||
(int) PixelFormat.Blue,
|
||||
(int) PixelFormat.Red,
|
||||
(int) PixelFormat.Green,
|
||||
(int) PixelFormat.Alpha
|
||||
];
|
||||
break;
|
||||
}
|
||||
// R: Roughness
|
||||
// G: Metallic
|
||||
// B: Whatever (AO / S / E / ...)
|
||||
case "CCFF7R":
|
||||
case "PJ033":
|
||||
case "ABINFINITE":
|
||||
{
|
||||
texture.SwizzleMask =
|
||||
[
|
||||
(int) PixelFormat.Blue,
|
||||
(int) PixelFormat.Green,
|
||||
(int) PixelFormat.Red,
|
||||
(int) PixelFormat.Alpha
|
||||
];
|
||||
break;
|
||||
}
|
||||
}
|
||||
texture.Swizzle();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,234 +0,0 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using FModel.Views.Snooper.Buffers;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using OpenTK.Windowing.Common;
|
||||
using OpenTK.Windowing.Common.Input;
|
||||
using OpenTK.Windowing.Desktop;
|
||||
using OpenTK.Windowing.GraphicsLibraryFramework;
|
||||
using SixLabors.ImageSharp.Advanced;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using Application = System.Windows.Application;
|
||||
|
||||
namespace FModel.Views.Snooper;
|
||||
|
||||
public class Snooper : GameWindow
|
||||
{
|
||||
public readonly FramebufferObject Framebuffer;
|
||||
public readonly Renderer Renderer;
|
||||
|
||||
private readonly SnimGui _gui;
|
||||
|
||||
private bool _init;
|
||||
|
||||
public Snooper(GameWindowSettings gwSettings, NativeWindowSettings nwSettings) : base(gwSettings, nwSettings)
|
||||
{
|
||||
Framebuffer = new FramebufferObject(ClientSize);
|
||||
Renderer = new Renderer(ClientSize.X, ClientSize.Y);
|
||||
|
||||
_gui = new SnimGui(ClientSize.X, ClientSize.Y);
|
||||
_init = false;
|
||||
}
|
||||
|
||||
public bool TryLoadExport(CancellationToken cancellationToken, UObject dummy, Lazy<UObject> export)
|
||||
{
|
||||
Renderer.Load(cancellationToken, dummy, export);
|
||||
return Renderer.Options.Models.Count > 0;
|
||||
}
|
||||
|
||||
public unsafe void WindowShouldClose(bool value, bool clear)
|
||||
{
|
||||
if (clear)
|
||||
{
|
||||
Renderer.CameraOp.Speed = 1f;
|
||||
Renderer.Save();
|
||||
}
|
||||
|
||||
GLFW.SetWindowShouldClose(WindowPtr, value); // start / stop game loop
|
||||
IsVisible = !value;
|
||||
}
|
||||
|
||||
public unsafe void WindowShouldFreeze(bool value)
|
||||
{
|
||||
GLFW.SetWindowShouldClose(WindowPtr, value); // start / stop game loop
|
||||
IsVisible = true;
|
||||
}
|
||||
|
||||
public override void Run()
|
||||
{
|
||||
Renderer.Options.SwapMaterial(false);
|
||||
Renderer.Options.AnimateMesh(false);
|
||||
Application.Current.Dispatcher.Invoke(delegate
|
||||
{
|
||||
WindowShouldClose(false, false);
|
||||
base.Run();
|
||||
});
|
||||
}
|
||||
|
||||
private unsafe void LoadWindowIcon()
|
||||
{
|
||||
var info = Application.GetResourceStream(new Uri("/FModel;component/Resources/engine.png", UriKind.Relative));
|
||||
using var img = SixLabors.ImageSharp.Image.Load<Rgba32>(info.Stream);
|
||||
var memoryGroup = img.GetPixelMemoryGroup();
|
||||
Memory<byte> array = new byte[memoryGroup.TotalLength * sizeof(Rgba32)];
|
||||
var block = MemoryMarshal.Cast<byte, Rgba32>(array.Span);
|
||||
foreach (var memory in memoryGroup)
|
||||
{
|
||||
memory.Span.CopyTo(block);
|
||||
block = block[memory.Length..];
|
||||
}
|
||||
|
||||
Icon = new WindowIcon(new OpenTK.Windowing.Common.Input.Image(img.Width, img.Height, array.ToArray()));
|
||||
}
|
||||
|
||||
protected override void OnLoad()
|
||||
{
|
||||
if (_init)
|
||||
{
|
||||
Renderer.Options.SetupModelsAndLights();
|
||||
return;
|
||||
}
|
||||
|
||||
base.OnLoad();
|
||||
CenterWindow();
|
||||
LoadWindowIcon();
|
||||
|
||||
GL.ClearColor(OpenTK.Mathematics.Color4.Black);
|
||||
GL.Enable(EnableCap.Blend);
|
||||
GL.Enable(EnableCap.CullFace);
|
||||
GL.Enable(EnableCap.DepthTest);
|
||||
GL.Enable(EnableCap.Multisample);
|
||||
GL.Enable(EnableCap.VertexProgramPointSize);
|
||||
GL.StencilOp(StencilOp.Keep, StencilOp.Replace, StencilOp.Replace);
|
||||
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
|
||||
|
||||
Framebuffer.Setup();
|
||||
Renderer.Setup();
|
||||
_init = true;
|
||||
}
|
||||
|
||||
private void ClearWhatHasBeenDrawn()
|
||||
{
|
||||
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit);
|
||||
}
|
||||
|
||||
protected override void OnRenderFrame(FrameEventArgs args)
|
||||
{
|
||||
base.OnRenderFrame(args);
|
||||
if (!IsVisible)
|
||||
return;
|
||||
|
||||
ClearWhatHasBeenDrawn(); // clear window background
|
||||
Framebuffer.Bind(); // switch to viewport background
|
||||
ClearWhatHasBeenDrawn(); // clear viewport background
|
||||
|
||||
Renderer.Render(); // render everything
|
||||
|
||||
Framebuffer.BindMsaa();
|
||||
Framebuffer.Bind(0); // switch to window background
|
||||
|
||||
_gui.Render(this); // render UI
|
||||
SwapBuffers();
|
||||
}
|
||||
|
||||
protected override void OnUpdateFrame(FrameEventArgs e)
|
||||
{
|
||||
base.OnUpdateFrame(e);
|
||||
if (!IsVisible)
|
||||
return;
|
||||
|
||||
var delta = (float) e.Time;
|
||||
|
||||
_gui.Controller.Update(this, delta);
|
||||
Renderer.Update(this, delta);
|
||||
}
|
||||
|
||||
protected override void OnTextInput(TextInputEventArgs e)
|
||||
{
|
||||
base.OnTextInput(e);
|
||||
if (!IsVisible)
|
||||
return;
|
||||
|
||||
_gui.Controller.PressChar((char) e.Unicode);
|
||||
}
|
||||
|
||||
protected override void OnResize(ResizeEventArgs e)
|
||||
{
|
||||
base.OnResize(e);
|
||||
|
||||
GL.Viewport(0, 0, e.Width, e.Height);
|
||||
|
||||
Framebuffer.WindowResized(e.Width, e.Height);
|
||||
Renderer.WindowResized(e.Width, e.Height);
|
||||
|
||||
_gui.Controller.WindowResized(e.Width, e.Height);
|
||||
}
|
||||
|
||||
protected override void OnClosing(CancelEventArgs e)
|
||||
{
|
||||
base.OnClosing(e);
|
||||
WindowShouldClose(true, true);
|
||||
}
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool EnumDisplaySettings(
|
||||
string deviceName, int modeNum, ref DEVMODE devMode);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct DEVMODE
|
||||
{
|
||||
private const int CCHDEVICENAME = 0x20;
|
||||
private const int CCHFORMNAME = 0x20;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)]
|
||||
public string dmDeviceName;
|
||||
public short dmSpecVersion;
|
||||
public short dmDriverVersion;
|
||||
public short dmSize;
|
||||
public short dmDriverExtra;
|
||||
public int dmFields;
|
||||
public int dmPositionX;
|
||||
public int dmPositionY;
|
||||
public ScreenOrientation dmDisplayOrientation;
|
||||
public int dmDisplayFixedOutput;
|
||||
public short dmColor;
|
||||
public short dmDuplex;
|
||||
public short dmYResolution;
|
||||
public short dmTTOption;
|
||||
public short dmCollate;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)]
|
||||
public string dmFormName;
|
||||
public short dmLogPixels;
|
||||
public int dmBitsPerPel;
|
||||
public int dmPelsWidth;
|
||||
public int dmPelsHeight;
|
||||
public int dmDisplayFlags;
|
||||
public int dmDisplayFrequency;
|
||||
public int dmICMMethod;
|
||||
public int dmICMIntent;
|
||||
public int dmMediaType;
|
||||
public int dmDitherType;
|
||||
public int dmReserved1;
|
||||
public int dmReserved2;
|
||||
public int dmPanningWidth;
|
||||
public int dmPanningHeight;
|
||||
|
||||
}
|
||||
|
||||
public static int GetMaxRefreshFrequency()
|
||||
{
|
||||
var rf = 60;
|
||||
var vDevMode = new DEVMODE();
|
||||
var i = 0;
|
||||
while (EnumDisplaySettings(null, i, ref vDevMode))
|
||||
{
|
||||
i++;
|
||||
rf = Math.Max(rf, vDevMode.dmDisplayFrequency);
|
||||
}
|
||||
|
||||
return rf;
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
using System.Numerics;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace FModel.Views.Snooper;
|
||||
|
||||
public class Transform
|
||||
{
|
||||
public static Transform Identity
|
||||
{
|
||||
get => new ();
|
||||
}
|
||||
|
||||
public Matrix4x4 Relation = Matrix4x4.Identity;
|
||||
public FVector Position = FVector.ZeroVector;
|
||||
public FQuat Rotation = FQuat.Identity;
|
||||
public FVector Scale = FVector.OneVector;
|
||||
|
||||
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 Save()
|
||||
{
|
||||
_saved = LocalMatrix;
|
||||
}
|
||||
|
||||
public void ModifyLocal(Matrix4x4 matrix)
|
||||
{
|
||||
Matrix4x4.Decompose(matrix, out var scale, out var rotation, out var position);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
if (!_saved.HasValue) return;
|
||||
ModifyLocal(_saved.Value);
|
||||
}
|
||||
|
||||
public void ImGuiTransform(float speed)
|
||||
{
|
||||
const float width = 100f;
|
||||
|
||||
if (ImGui.TreeNode("Position"))
|
||||
{
|
||||
ImGui.SetNextItemWidth(width);
|
||||
ImGui.DragFloat("X", ref Position.X, speed, 0f, 0f, "%.2f m");
|
||||
|
||||
ImGui.SetNextItemWidth(width);
|
||||
ImGui.DragFloat("Y", ref Position.Y, speed, 0f, 0f, "%.2f m");
|
||||
|
||||
ImGui.SetNextItemWidth(width);
|
||||
ImGui.DragFloat("Z", ref Position.Z, speed, 0f, 0f, "%.2f m");
|
||||
|
||||
ImGui.TreePop();
|
||||
}
|
||||
|
||||
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.Y, .005f, 0f, 0f, "%.3f rad");
|
||||
|
||||
ImGui.SetNextItemWidth(width);
|
||||
ImGui.DragFloat("Z", ref Rotation.Z, .005f, 0f, 0f, "%.3f rad");
|
||||
|
||||
ImGui.TreePop();
|
||||
}
|
||||
|
||||
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.Y, speed, 0f, 0f, "%.3f");
|
||||
|
||||
ImGui.SetNextItemWidth(width);
|
||||
ImGui.DragFloat("Z", ref Scale.Z, speed, 0f, 0f, "%.3f");
|
||||
|
||||
ImGui.TreePop();
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() => Matrix.Translation.ToString();
|
||||
}
|
||||
1
Snooper
Submodule
1
Snooper
Submodule
Submodule Snooper added at b6ce504513
Reference in New Issue
Block a user