Merge pull request #337 from 4sval/snooper-tk

snooper
This commit is contained in:
Valentin
2022-12-04 23:58:05 +01:00
committed by GitHub
84 changed files with 5438 additions and 1344 deletions

View File

@@ -45,4 +45,4 @@ jobs:
automatic_release_tag: ${{ github.event.inputs.appVersion }}
repo_token: ${{ secrets.GITHUB_TOKEN }}
prerelease: false
files: FModel.zip
files: FModel.zip

2
.gitmodules vendored
View File

@@ -3,4 +3,4 @@
url = https://github.com/FabianFG/CUE4Parse
[submodule "EpicManifestParser"]
path = EpicManifestParser
url = https://github.com/FModel/EpicManifestParser
url = https://github.com/FModel/EpicManifestParser

View File

@@ -9,6 +9,9 @@ public static class Constants
public const string ZERO_64_CHAR = "0000000000000000000000000000000000000000000000000000000000000000";
public static readonly FGuid ZERO_GUID = new(0U);
public const float SCALE_DOWN_RATIO = 0.01F;
public const int SAMPLES_COUNT = 4;
public const string WHITE = "#DAE5F2";
public const string GRAY = "#BBBBBB";
public const string RED = "#E06C75";

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using CUE4Parse.GameTypes.FN.Enums;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Engine;
using CUE4Parse.UE4.Assets.Exports.Material;
@@ -12,7 +13,6 @@ using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.GameplayTags;
using CUE4Parse.UE4.Objects.UObject;
using CUE4Parse_Conversion.Textures;
using CUE4Parse.GameTypes.FN.Enums;
using FModel.Settings;
using SkiaSharp;
@@ -25,9 +25,7 @@ public class BaseIcon : UCreator
protected string CosmeticSource { get; set; }
protected Dictionary<string, SKBitmap> UserFacingFlags { get; set; }
public BaseIcon(UObject uObject, EIconStyle style) : base(uObject, style)
{
}
public BaseIcon(UObject uObject, EIconStyle style) : base(uObject, style) { }
public void ParseForReward(bool isUsingDisplayAsset)
{
@@ -198,26 +196,39 @@ public class BaseIcon : UCreator
protected string GetCosmeticSeason(string seasonNumber)
{
var s = seasonNumber["Cosmetics.Filter.Season.".Length..];
var number = int.Parse(s);
var initial = int.Parse(s);
var number = initial;
switch (number)
var chapterIdx = 0;
var seasonIdx = 0;
while (number > 0)
{
case 10:
s = "X";
break;
case > 18:
number += 2;
s = number.ToString();
break;
var seasonsInChapter = GetSeasonsInChapter(++chapterIdx);
if (number > seasonsInChapter)
number -= seasonsInChapter;
else
{
seasonIdx = number;
number = 0;
}
}
static int GetSeasonsInChapter(int chapter) => chapter switch
{
1 => 10,
2 => 8,
3 => 4,
_ => 10
};
var season = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "SeasonTextFormat", "Season {0}");
var introduced = Utils.GetLocalizedResource("Fort.Cosmetics", "CosmeticItemDescription_Season", "\nIntroduced in <SeasonText>{0}</>.");
if (number <= 10) return Utils.RemoveHtmlTags(string.Format(introduced, string.Format(season, s)));
if (initial <= 10) return Utils.RemoveHtmlTags(string.Format(introduced, string.Format(season, s)));
var chapter = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterTextFormat", "Chapter {0}");
var chapterFormat = Utils.GetLocalizedResource("AthenaSeasonItemDefinitionInternal", "ChapterSeasonTextFormat", "{0}, {1}");
var d = string.Format(chapterFormat, string.Format(chapter, number / 10 + 1), string.Format(season, s[^1..]));
var d = string.Format(chapterFormat, string.Format(chapter, chapterIdx), string.Format(season, seasonIdx));
return Utils.RemoveHtmlTags(string.Format(introduced, d));
}

View File

@@ -204,6 +204,9 @@ public static class Utils
return string.Empty;
}
public static string FixPath(string weirdPath) =>
_applicationView.CUE4Parse.Provider.FixPath(weirdPath, StringComparison.Ordinal);
public static void DrawCenteredMultilineText(SKCanvas c, string text, int maxCount, int size, int margin, SKTextAlign side, SKRect area, SKPaint paint)
{
var lineHeight = paint.TextSize * 1.2f;

View File

@@ -95,6 +95,8 @@ public enum FGame
PandaGame,
[Description("Tower of Fantasy")]
Hotta,
[Description("eFootball 2023")]
eFootball
}
public enum ELoadingMode

View File

@@ -5,9 +5,9 @@
<TargetFramework>net6.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<ApplicationIcon>FModel.ico</ApplicationIcon>
<Version>4.3.2</Version>
<AssemblyVersion>4.3.2.1</AssemblyVersion>
<FileVersion>4.3.2.1</FileVersion>
<Version>4.4.0</Version>
<AssemblyVersion>4.4.0.0</AssemblyVersion>
<FileVersion>4.4.0.0</FileVersion>
<IsPackable>false</IsPackable>
<IsPublishable>true</IsPublishable>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
@@ -93,6 +93,20 @@
<None Remove="Resources\edit.png" />
<None Remove="Resources\go_to_directory.png" />
<None Remove="Resources\npcleftside.png" />
<None Remove="Resources\default.frag" />
<None Remove="Resources\default.vert" />
<None Remove="Resources\grid.frag" />
<None Remove="Resources\grid.vert" />
<None Remove="Resources\skybox.frag" />
<None Remove="Resources\skybox.vert" />
<None Remove="Resources\framebuffer.frag" />
<None Remove="Resources\framebuffer.vert" />
<None Remove="Resources\outline.frag" />
<None Remove="Resources\outline.vert" />
<None Remove="Resources\picking.frag" />
<None Remove="Resources\picking.vert" />
<None Remove="Resources\light.frag" />
<None Remove="Resources\light.vert" />
</ItemGroup>
<ItemGroup>
@@ -102,6 +116,20 @@
<EmbeddedResource Include="Resources\Xml.xshd" />
<EmbeddedResource Include="Resources\Cpp.xshd" />
<EmbeddedResource Include="Resources\Changelog.xshd" />
<EmbeddedResource Include="Resources\default.frag" />
<EmbeddedResource Include="Resources\default.vert" />
<EmbeddedResource Include="Resources\grid.frag" />
<EmbeddedResource Include="Resources\grid.vert" />
<EmbeddedResource Include="Resources\skybox.frag" />
<EmbeddedResource Include="Resources\skybox.vert" />
<EmbeddedResource Include="Resources\framebuffer.frag" />
<EmbeddedResource Include="Resources\framebuffer.vert" />
<EmbeddedResource Include="Resources\outline.frag" />
<EmbeddedResource Include="Resources\outline.vert" />
<EmbeddedResource Include="Resources\picking.frag" />
<EmbeddedResource Include="Resources\picking.vert" />
<EmbeddedResource Include="Resources\light.frag" />
<EmbeddedResource Include="Resources\light.vert" />
</ItemGroup>
<ItemGroup>
@@ -111,15 +139,17 @@
<PackageReference Include="AvalonEdit" Version="6.1.3.50" />
<PackageReference Include="CSCore" Version="1.2.1.2" />
<PackageReference Include="DiscordRichPresence" Version="1.0.175" />
<PackageReference Include="HelixToolkit.SharpDX.Core.Wpf" Version="2.21.0" />
<PackageReference Include="ImGui.NET" Version="1.88.0" />
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.2.16" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="NVorbis" Version="0.10.4" />
<PackageReference Include="Oodle.NET" Version="1.0.1" />
<PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.1" />
<PackageReference Include="OpenTK" Version="4.7.5" />
<PackageReference Include="RestSharp" Version="108.0.1" />
<PackageReference Include="Serilog" Version="2.11.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
<PackageReference Include="SkiaSharp.HarfBuzz" Version="2.88.0" />
<PackageReference Include="SkiaSharp.Svg" Version="1.60.0" />
</ItemGroup>
@@ -190,6 +220,14 @@
<Resource Include="Resources\go_to_directory.png" />
<Resource Include="Resources\approaching_storm_cubemap.dds" />
<Resource Include="Resources\npcleftside.png" />
<Resource Include="Resources\nx.png" />
<Resource Include="Resources\ny.png" />
<Resource Include="Resources\nz.png" />
<Resource Include="Resources\px.png" />
<Resource Include="Resources\py.png" />
<Resource Include="Resources\pz.png" />
<Resource Include="Resources\pointlight.png" />
<Resource Include="Resources\spotlight.png" />
</ItemGroup>
<ItemGroup>

View File

@@ -0,0 +1,45 @@
namespace FModel.Framework;
public class FStatus : ViewModel
{
private bool _isReady;
public bool IsReady
{
get => _isReady;
private set => SetProperty(ref _isReady, value);
}
private EStatusKind _kind;
public EStatusKind Kind
{
get => _kind;
private set
{
SetProperty(ref _kind, value);
IsReady = Kind != EStatusKind.Loading && Kind != EStatusKind.Stopping;
}
}
private string _label;
public string Label
{
get => _label;
private set => SetProperty(ref _label, value);
}
public FStatus()
{
SetStatus(EStatusKind.Loading);
}
public void SetStatus(EStatusKind kind, string label = "")
{
Kind = kind;
UpdateStatusLabel(label);
}
public void UpdateStatusLabel(string label)
{
Label = Kind == EStatusKind.Loading ? $"{Kind} {label}".Trim() : Kind.ToString();
}
}

View File

@@ -0,0 +1,637 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
using ImGuiNET;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Windowing.Desktop;
using OpenTK.Windowing.GraphicsLibraryFramework;
using ErrorCode = OpenTK.Graphics.OpenGL4.ErrorCode;
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;
// private string _iniPath;
private ImFontPtr _normal;
private ImFontPtr _bold;
private ImFontPtr _semiBold;
private readonly Vector2 _scaleFactor = Vector2.One;
private static bool KHRDebugAvailable = false;
public ImGuiController(int width, int height)
{
_windowWidth = width;
_windowHeight = height;
// _iniPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "imgui.ini");
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);
// ImGui.LoadIniSettingsFromDisk(_iniPath);
var io = ImGui.GetIO();
_normal = io.Fonts.AddFontFromFileTTF("C:\\Windows\\Fonts\\segoeui.ttf", 16);
_bold = io.Fonts.AddFontFromFileTTF("C:\\Windows\\Fonts\\segoeuib.ttf", 16);
_semiBold = io.Fonts.AddFontFromFileTTF("C:\\Windows\\Fonts\\seguisb.ttf", 16);
io.BackendFlags |= ImGuiBackendFlags.RendererHasVtxOffset;
io.ConfigFlags |= ImGuiConfigFlags.NavEnableKeyboard;
io.ConfigFlags |= ImGuiConfigFlags.DockingEnable;
io.Fonts.Flags |= ImFontAtlasFlags.NoBakedLines;
CreateDeviceResources();
SetKeyMappings();
SetPerFrameImGuiData(1f / 60f);
ImGui.NewFrame();
_frameBegun = true;
}
public void Bold() => PushFont(_bold);
public void SemiBold() => PushFont(_semiBold);
public void PopFont()
{
ImGui.PopFont();
PushFont(_normal);
}
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 = Unsafe.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());
}
CheckGLError("End of frame");
}
/// <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();
}
/// <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();
// if (io.WantSaveIniSettings) ImGui.SaveIniSettingsToDisk(_iniPath);
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(typeof(Keys)))
{
if (key == Keys.Unknown || io.KeyMap[(int) key] == -1) continue;
io.AddKeyEvent((ImGuiKey) io.KeyMap[(int) 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);
}
public void PressChar(char keyChar)
{
PressedChars.Add(keyChar);
}
private static void SetKeyMappings()
{
ImGuiIOPtr io = ImGui.GetIO();
io.KeyMap[(int)ImGuiKey.LeftShift] = (int)Keys.LeftShift;
io.KeyMap[(int)ImGuiKey.RightShift] = (int)Keys.RightShift;
io.KeyMap[(int)ImGuiKey.LeftCtrl] = (int)Keys.LeftControl;
io.KeyMap[(int)ImGuiKey.RightCtrl] = (int)Keys.RightControl;
io.KeyMap[(int)ImGuiKey.LeftAlt] = (int)Keys.LeftAlt;
io.KeyMap[(int)ImGuiKey.RightAlt] = (int)Keys.RightAlt;
io.KeyMap[(int)ImGuiKey.LeftSuper] = (int)Keys.LeftSuper;
io.KeyMap[(int)ImGuiKey.RightSuper] = (int)Keys.RightSuper;
io.KeyMap[(int)ImGuiKey.Menu] = (int)Keys.Menu;
io.KeyMap[(int)ImGuiKey.UpArrow] = (int)Keys.Up;
io.KeyMap[(int)ImGuiKey.DownArrow] = (int)Keys.Down;
io.KeyMap[(int)ImGuiKey.LeftArrow] = (int)Keys.Left;
io.KeyMap[(int)ImGuiKey.RightArrow] = (int)Keys.Right;
io.KeyMap[(int)ImGuiKey.Enter] = (int)Keys.Enter;
io.KeyMap[(int)ImGuiKey.Escape] = (int)Keys.Escape;
io.KeyMap[(int)ImGuiKey.Space] = (int)Keys.Space;
io.KeyMap[(int)ImGuiKey.Tab] = (int)Keys.Tab;
io.KeyMap[(int)ImGuiKey.Backspace] = (int)Keys.Backspace;
io.KeyMap[(int)ImGuiKey.Insert] = (int)Keys.Insert;
io.KeyMap[(int)ImGuiKey.Delete] = (int)Keys.Delete;
io.KeyMap[(int)ImGuiKey.PageUp] = (int)Keys.PageUp;
io.KeyMap[(int)ImGuiKey.PageDown] = (int)Keys.PageDown;
io.KeyMap[(int)ImGuiKey.Home] = (int)Keys.Home;
io.KeyMap[(int)ImGuiKey.End] = (int)Keys.End;
io.KeyMap[(int)ImGuiKey.CapsLock] = (int)Keys.CapsLock;
io.KeyMap[(int)ImGuiKey.ScrollLock] = (int)Keys.ScrollLock;
io.KeyMap[(int)ImGuiKey.PrintScreen] = (int)Keys.PrintScreen;
io.KeyMap[(int)ImGuiKey.Pause] = (int)Keys.Pause;
io.KeyMap[(int)ImGuiKey.NumLock] = (int)Keys.NumLock;
io.KeyMap[(int)ImGuiKey.KeypadDivide] = (int)Keys.KeyPadDivide;
io.KeyMap[(int)ImGuiKey.KeypadMultiply] = (int)Keys.KeyPadMultiply;
io.KeyMap[(int)ImGuiKey.KeypadSubtract] = (int)Keys.KeyPadSubtract;
io.KeyMap[(int)ImGuiKey.KeypadAdd] = (int)Keys.KeyPadAdd;
io.KeyMap[(int)ImGuiKey.KeypadDecimal] = (int)Keys.KeyPadDecimal;
io.KeyMap[(int)ImGuiKey.KeypadEnter] = (int)Keys.KeyPadEnter;
io.KeyMap[(int)ImGuiKey.GraveAccent] = (int)Keys.GraveAccent;
io.KeyMap[(int)ImGuiKey.Minus] = (int)Keys.Minus;
io.KeyMap[(int)ImGuiKey.Equal] = (int)Keys.Equal;
io.KeyMap[(int)ImGuiKey.LeftBracket] = (int)Keys.LeftBracket;
io.KeyMap[(int)ImGuiKey.RightBracket] = (int)Keys.RightBracket;
io.KeyMap[(int)ImGuiKey.Semicolon] = (int)Keys.Semicolon;
io.KeyMap[(int)ImGuiKey.Apostrophe] = (int)Keys.Apostrophe;
io.KeyMap[(int)ImGuiKey.Comma] = (int)Keys.Comma;
io.KeyMap[(int)ImGuiKey.Period] = (int)Keys.Period;
io.KeyMap[(int)ImGuiKey.Slash] = (int)Keys.Slash;
io.KeyMap[(int)ImGuiKey.Backslash] = (int)Keys.Backslash;
io.KeyMap[(int)ImGuiKey.F1] = (int)Keys.F1;
io.KeyMap[(int)ImGuiKey.F2] = (int)Keys.F2;
io.KeyMap[(int)ImGuiKey.F3] = (int)Keys.F3;
io.KeyMap[(int)ImGuiKey.F4] = (int)Keys.F4;
io.KeyMap[(int)ImGuiKey.F5] = (int)Keys.F5;
io.KeyMap[(int)ImGuiKey.F6] = (int)Keys.F6;
io.KeyMap[(int)ImGuiKey.F7] = (int)Keys.F7;
io.KeyMap[(int)ImGuiKey.F8] = (int)Keys.F8;
io.KeyMap[(int)ImGuiKey.F9] = (int)Keys.F9;
io.KeyMap[(int)ImGuiKey.F10] = (int)Keys.F10;
io.KeyMap[(int)ImGuiKey.F11] = (int)Keys.F11;
io.KeyMap[(int)ImGuiKey.F12] = (int)Keys.F12;
io.KeyMap[(int)ImGuiKey.Keypad0] = (int)Keys.KeyPad0;
io.KeyMap[(int)ImGuiKey.Keypad1] = (int)Keys.KeyPad1;
io.KeyMap[(int)ImGuiKey.Keypad2] = (int)Keys.KeyPad2;
io.KeyMap[(int)ImGuiKey.Keypad3] = (int)Keys.KeyPad3;
io.KeyMap[(int)ImGuiKey.Keypad4] = (int)Keys.KeyPad4;
io.KeyMap[(int)ImGuiKey.Keypad5] = (int)Keys.KeyPad5;
io.KeyMap[(int)ImGuiKey.Keypad6] = (int)Keys.KeyPad6;
io.KeyMap[(int)ImGuiKey.Keypad7] = (int)Keys.KeyPad7;
io.KeyMap[(int)ImGuiKey.Keypad8] = (int)Keys.KeyPad8;
io.KeyMap[(int)ImGuiKey.Keypad9] = (int)Keys.KeyPad9;
io.KeyMap[(int)ImGuiKey._0] = (int)Keys.D0;
io.KeyMap[(int)ImGuiKey._1] = (int)Keys.D1;
io.KeyMap[(int)ImGuiKey._2] = (int)Keys.D2;
io.KeyMap[(int)ImGuiKey._3] = (int)Keys.D3;
io.KeyMap[(int)ImGuiKey._4] = (int)Keys.D4;
io.KeyMap[(int)ImGuiKey._5] = (int)Keys.D5;
io.KeyMap[(int)ImGuiKey._6] = (int)Keys.D6;
io.KeyMap[(int)ImGuiKey._7] = (int)Keys.D7;
io.KeyMap[(int)ImGuiKey._8] = (int)Keys.D8;
io.KeyMap[(int)ImGuiKey._9] = (int)Keys.D9;
io.KeyMap[(int)ImGuiKey.A] = (int)Keys.A;
io.KeyMap[(int)ImGuiKey.B] = (int)Keys.B;
io.KeyMap[(int)ImGuiKey.C] = (int)Keys.C;
io.KeyMap[(int)ImGuiKey.D] = (int)Keys.D;
io.KeyMap[(int)ImGuiKey.E] = (int)Keys.E;
io.KeyMap[(int)ImGuiKey.F] = (int)Keys.F;
io.KeyMap[(int)ImGuiKey.G] = (int)Keys.G;
io.KeyMap[(int)ImGuiKey.H] = (int)Keys.H;
io.KeyMap[(int)ImGuiKey.I] = (int)Keys.I;
io.KeyMap[(int)ImGuiKey.J] = (int)Keys.J;
io.KeyMap[(int)ImGuiKey.K] = (int)Keys.K;
io.KeyMap[(int)ImGuiKey.L] = (int)Keys.L;
io.KeyMap[(int)ImGuiKey.M] = (int)Keys.M;
io.KeyMap[(int)ImGuiKey.N] = (int)Keys.N;
io.KeyMap[(int)ImGuiKey.O] = (int)Keys.O;
io.KeyMap[(int)ImGuiKey.P] = (int)Keys.P;
io.KeyMap[(int)ImGuiKey.Q] = (int)Keys.Q;
io.KeyMap[(int)ImGuiKey.R] = (int)Keys.R;
io.KeyMap[(int)ImGuiKey.S] = (int)Keys.S;
io.KeyMap[(int)ImGuiKey.T] = (int)Keys.T;
io.KeyMap[(int)ImGuiKey.U] = (int)Keys.U;
io.KeyMap[(int)ImGuiKey.V] = (int)Keys.V;
io.KeyMap[(int)ImGuiKey.W] = (int)Keys.W;
io.KeyMap[(int)ImGuiKey.X] = (int)Keys.X;
io.KeyMap[(int)ImGuiKey.Y] = (int)Keys.Y;
io.KeyMap[(int)ImGuiKey.Z] = (int)Keys.Z;
}
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.CmdListsRange[i];
int vertexSize = cmd_list.VtxBuffer.Size * Unsafe.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.CmdListsRange[n];
GL.BufferSubData(BufferTarget.ArrayBuffer, IntPtr.Zero, cmd_list.VtxBuffer.Size * Unsafe.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}");
}
}
}

View File

@@ -100,4 +100,14 @@ public static class Helper
var d = (Math.Abs(d1) + Math.Abs(d2) + 10) * 1.0e-15;
return -d < n && d > n;
}
public static float DegreesToRadians(float degrees)
{
return MathF.PI / 180f * degrees;
}
public static float RadiansToDegrees(float radians)
{
return radians* 180f / MathF.PI;
}
}

View File

@@ -46,7 +46,7 @@
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="AES" Command="{Binding MenuCommand}" CommandParameter="Directory_AES" IsEnabled="{Binding IsReady}">
<MenuItem Header="AES" Command="{Binding MenuCommand}" CommandParameter="Directory_AES" IsEnabled="{Binding Status.IsReady}">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
@@ -55,7 +55,7 @@
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Backup" Command="{Binding MenuCommand}" CommandParameter="Directory_Backup" IsEnabled="{Binding IsReady}">
<MenuItem Header="Backup" Command="{Binding MenuCommand}" CommandParameter="Directory_Backup" IsEnabled="{Binding Status.IsReady}">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
@@ -64,7 +64,7 @@
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Archives Info" Command="{Binding MenuCommand}" CommandParameter="Directory_ArchivesInfo" IsEnabled="{Binding IsReady}">
<MenuItem Header="Archives Info" Command="{Binding MenuCommand}" CommandParameter="Directory_ArchivesInfo" IsEnabled="{Binding Status.IsReady}">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
@@ -75,7 +75,7 @@
</MenuItem>
</MenuItem>
<MenuItem Header="Packages">
<MenuItem Header="Search" IsEnabled="{Binding IsReady}" InputGestureText="Ctrl+Shift+F" Click="OnSearchViewClick">
<MenuItem Header="Search" IsEnabled="{Binding Status.IsReady}" InputGestureText="Ctrl+Shift+F" Click="OnSearchViewClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
@@ -84,7 +84,7 @@
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Directories" ItemsSource="{Binding CustomDirectories.Directories}" IsEnabled="{Binding IsReady}">
<MenuItem Header="Directories" ItemsSource="{Binding CustomDirectories.Directories}" IsEnabled="{Binding Status.IsReady}">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
@@ -159,6 +159,15 @@
</MenuItem>
</MenuItem>
<MenuItem Header="Views">
<MenuItem Header="3D Viewer" Command="{Binding MenuCommand}" CommandParameter="Views_3dViewer">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.AccentForegroundBrush}}" Data="{StaticResource MeshIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Audio Player" Command="{Binding MenuCommand}" CommandParameter="Views_AudioPlayer">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
@@ -168,7 +177,7 @@
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Map Viewer" Command="{Binding MenuCommand}" CommandParameter="Views_MapViewer" IsEnabled="{Binding IsReady}">
<MenuItem Header="Map Viewer" Command="{Binding MenuCommand}" CommandParameter="Views_MapViewer" IsEnabled="{Binding Status.IsReady}">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
@@ -272,7 +281,7 @@
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Loading Mode" VerticalAlignment="Center" />
<ComboBox Grid.Row="0" Grid.Column="2" ItemsSource="{Binding LoadingModes.Modes}" IsEnabled="{Binding IsReady}"
<ComboBox Grid.Row="0" Grid.Column="2" ItemsSource="{Binding LoadingModes.Modes}" IsEnabled="{Binding Status.IsReady}"
SelectedItem="{Binding LoadingMode, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
@@ -281,7 +290,7 @@
</ComboBox.ItemTemplate>
</ComboBox>
<Button Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3" Content="Load"
Command="{Binding LoadingModes.LoadCommand}" IsEnabled="{Binding IsReady}"
Command="{Binding LoadingModes.LoadCommand}" IsEnabled="{Binding Status.IsReady}"
CommandParameter="{Binding SelectedItems, ElementName=DirectoryFilesListBox}" />
</Grid>
<Grid DockPanel.Dock="Top">
@@ -703,27 +712,27 @@
<Style TargetType="{x:Type StatusBar}" BasedOn="{StaticResource {x:Type StatusBar}}">
<Style.Triggers>
<!--don't mind me, MultiDataTrigger just sucks-->
<DataTrigger Binding="{Binding Status}" Value="{x:Static local:EStatusKind.Ready}">
<DataTrigger Binding="{Binding Status.Kind}" Value="{x:Static local:EStatusKind.Ready}">
<Setter Property="Background" Value="{DynamicResource {x:Static adonisUi:Brushes.AccentBrush}}" />
<Setter Property="Foreground" Value="White" />
</DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="{x:Static local:EStatusKind.Completed}">
<DataTrigger Binding="{Binding Status.Kind}" Value="{x:Static local:EStatusKind.Completed}">
<Setter Property="Background" Value="{DynamicResource {x:Static adonisUi:Brushes.AccentBrush}}" />
<Setter Property="Foreground" Value="White" />
</DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="{x:Static local:EStatusKind.Loading}">
<DataTrigger Binding="{Binding Status.Kind}" Value="{x:Static local:EStatusKind.Loading}">
<Setter Property="Background" Value="{DynamicResource {x:Static adonisUi:Brushes.AlertBrush}}" />
<Setter Property="Foreground" Value="White" />
</DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="{x:Static local:EStatusKind.Stopping}">
<DataTrigger Binding="{Binding Status.Kind}" Value="{x:Static local:EStatusKind.Stopping}">
<Setter Property="Background" Value="{DynamicResource {x:Static adonisUi:Brushes.AlertBrush}}" />
<Setter Property="Foreground" Value="White" />
</DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="{x:Static local:EStatusKind.Stopped}">
<DataTrigger Binding="{Binding Status.Kind}" Value="{x:Static local:EStatusKind.Stopped}">
<Setter Property="Background" Value="{DynamicResource {x:Static adonisUi:Brushes.ErrorBrush}}" />
<Setter Property="Foreground" Value="White" />
</DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="{x:Static local:EStatusKind.Failed}">
<DataTrigger Binding="{Binding Status.Kind}" Value="{x:Static local:EStatusKind.Failed}">
<Setter Property="Background" Value="{DynamicResource {x:Static adonisUi:Brushes.ErrorBrush}}" />
<Setter Property="Foreground" Value="White" />
</DataTrigger>
@@ -766,17 +775,17 @@
<TextBlock>
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Text" Value="{Binding Status}" />
<Setter Property="Text" Value="{Binding Status.Label}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Status}" Value="{x:Static local:EStatusKind.Loading}">
<Setter Property="Text" Value="{Binding Status, StringFormat='{}{0} …'}" />
<DataTrigger Binding="{Binding Status.Kind}" Value="{x:Static local:EStatusKind.Loading}">
<Setter Property="Text" Value="{Binding Status.Label, StringFormat='{}{0} …'}" />
</DataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding CanBeCanceled, Source={x:Static local:Services.ApplicationService.ThreadWorkerView}}" Value="True" />
<Condition Binding="{Binding Status}" Value="{x:Static local:EStatusKind.Loading}" />
<Condition Binding="{Binding Status.Kind}" Value="{x:Static local:EStatusKind.Loading}" />
</MultiDataTrigger.Conditions>
<Setter Property="Text" Value="{Binding Status, StringFormat='{}{0} … ESC to Cancel'}" />
<Setter Property="Text" Value="{Binding Status.Label, StringFormat='{}{0} … ESC to Cancel'}" />
</MultiDataTrigger>
</Style.Triggers>
</Style>

View File

@@ -70,11 +70,24 @@ public partial class MainWindow
await _applicationView.CUE4Parse.InitInformation();
#endif
await _applicationView.CUE4Parse.InitMappings();
await _applicationView.InitImGuiSettings();
await _applicationView.InitVgmStream();
await _applicationView.InitOodle();
if (UserSettings.Default.DiscordRpc == EDiscordRpc.Always)
_discordHandler.Initialize(_applicationView.CUE4Parse.Game);
#if DEBUG
// await _threadWorkerView.Begin(cancellationToken =>
// _applicationView.CUE4Parse.Extract(cancellationToken,
// "FortniteGame/Content/Characters/Player/Female/Medium/Bodies/F_MED_RoseDust/Meshes/F_MED_RoseDust.uasset"));
// await _threadWorkerView.Begin(cancellationToken =>
// _applicationView.CUE4Parse.Extract(cancellationToken,
// "fortnitegame/Content/Accessories/FORT_Backpacks/Backpack_M_MED_Despair/Meshes/M_MED_Despair_Pack.uasset"));
// await _threadWorkerView.Begin(cancellationToken =>
// _applicationView.CUE4Parse.Extract(cancellationToken,
// "FortniteGame/Content/Animation/Game/MainPlayer/Emotes/Acrobatic_Superhero/Emote_AcrobaticSuperhero_CMM.uasset"));
#endif
}
private void OnGridSplitterDoubleClick(object sender, MouseButtonEventArgs e)
@@ -89,10 +102,10 @@ public partial class MainWindow
if (_threadWorkerView.CanBeCanceled && e.Key == Key.Escape)
{
_applicationView.Status = EStatusKind.Stopping;
_applicationView.Status.SetStatus(EStatusKind.Stopping);
_threadWorkerView.Cancel();
}
else if (_applicationView.IsReady && e.Key == Key.F && Keyboard.Modifiers.HasFlag(ModifierKeys.Control) && Keyboard.Modifiers.HasFlag(ModifierKeys.Shift))
else if (_applicationView.Status.IsReady && e.Key == Key.F && Keyboard.Modifiers.HasFlag(ModifierKeys.Control) && Keyboard.Modifiers.HasFlag(ModifierKeys.Shift))
OnSearchViewClick(null, null);
else if (e.Key == Key.Left && _applicationView.CUE4Parse.TabControl.SelectedTab.HasImage)
_applicationView.CUE4Parse.TabControl.SelectedTab.GoPreviousImage();
@@ -224,14 +237,14 @@ public partial class MainWindow
private void OnMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (!_applicationView.IsReady || sender is not ListBox listBox) return;
if (!_applicationView.Status.IsReady || sender is not ListBox listBox) return;
UserSettings.Default.LoadingMode = ELoadingMode.Multiple;
_applicationView.LoadingModes.LoadCommand.Execute(listBox.SelectedItems);
}
private async void OnPreviewKeyDown(object sender, KeyEventArgs e)
{
if (!_applicationView.IsReady || sender is not ListBox listBox) return;
if (!_applicationView.Status.IsReady || sender is not ListBox listBox) return;
switch (e.Key)
{

View File

@@ -0,0 +1,221 @@
#version 330 core
#define PI 3.1415926535897932384626433832795
#define MAX_UV_COUNT 8
#define MAX_LIGHT_COUNT 100
in vec3 fPos;
in vec3 fNormal;
in vec3 fTangent;
in vec2 fTexCoords;
in float fTexLayer;
in vec4 fColor;
struct Texture
{
sampler2D Sampler;
vec4 Color;
};
struct Boost
{
vec3 Color;
float Exponent;
};
struct AoParams
{
sampler2D Sampler;
float AmbientOcclusion;
Boost ColorBoost;
bool HasColorBoost;
};
struct Parameters
{
Texture Diffuse[MAX_UV_COUNT];
Texture Normals[MAX_UV_COUNT];
Texture SpecularMasks[MAX_UV_COUNT];
Texture Emissive[MAX_UV_COUNT];
AoParams Ao;
bool HasAo;
float Roughness;
float EmissiveMult;
float UVScale;
};
struct Light {
vec4 Color;
vec3 Position;
float Intensity;
vec2 Direction;
float ConeAngle;
float Attenuation;
float Linear;
float Quadratic;
int Type; // 0 Point, 1 Spot
};
uniform Parameters uParameters;
uniform Light uLights[MAX_LIGHT_COUNT];
uniform int uNumLights;
uniform int uNumTexCoords;
uniform vec3 uViewPos;
uniform vec3 uViewDir;
uniform bool bVertexColors[6];
out vec4 FragColor;
int LayerToIndex()
{
return min(int(fTexLayer), uNumTexCoords - 1);
}
vec4 SamplerToVector(sampler2D s)
{
return texture(s, fTexCoords * uParameters.UVScale);
}
vec3 ComputeNormals(int layer)
{
vec3 normal = SamplerToVector(uParameters.Normals[layer].Sampler).rgb * 2.0 - 1.0;
vec3 t = normalize(fTangent);
vec3 n = normalize(fNormal);
vec3 b = -normalize(cross(n, t));
mat3 tbn = mat3(t, b, n);
return normalize(tbn * normal);
}
vec3 schlickFresnel(int layer, float vDotH)
{
vec3 f0 = vec3(0.04f);
return f0 + (1.0f - f0) * pow(clamp(1.0f - vDotH, 0.0f, 1.0f), 5);
}
float ggxDistribution(float roughness, float nDoth)
{
float alpha2 = roughness * roughness * roughness * roughness;
float d = nDoth * nDoth * (alpha2- 1.0f) + 1.0f;
return alpha2 / (PI * d * d);
}
float geomSmith(float roughness, float dp)
{
float k = (roughness + 1.0f) * (roughness + 1.0f) / 8.0f;
float denom = dp * (1.0f - k) + k;
return dp / denom;
}
vec3 CalcCameraLight(int layer, vec3 normals)
{
vec3 specular_masks = SamplerToVector(uParameters.SpecularMasks[layer].Sampler).rgb;
float roughness = max(0.0f, mix(specular_masks.r, specular_masks.b, uParameters.Roughness));
vec3 intensity = vec3(1.0f) * 1.0f;
vec3 l = -uViewDir;
vec3 n = normals;
vec3 v = normalize(uViewPos - fPos);
vec3 h = normalize(v + l);
float nDotH = max(dot(n, h), 0.0f);
float vDotH = max(dot(v, h), 0.0f);
float nDotL = max(dot(n, l), 0.0f);
float nDotV = max(dot(n, v), 0.0f);
vec3 f = schlickFresnel(layer, vDotH);
vec3 kS = f;
vec3 kD = 1.0 - kS;
kD *= max(0.0f, dot(v, reflect(-v, normals)) * specular_masks.g);
vec3 specBrdfNom = ggxDistribution(roughness, nDotH) * f * geomSmith(roughness, nDotL) * geomSmith(roughness, nDotV);
float specBrdfDenom = 4.0f * nDotV * nDotL + 0.0001f;
vec3 specBrdf = specBrdfNom / specBrdfDenom;
vec3 fLambert = SamplerToVector(uParameters.Diffuse[layer].Sampler).rgb * uParameters.Diffuse[layer].Color.rgb;
vec3 diffuseBrdf = kD * fLambert / PI;
return (diffuseBrdf + specBrdf) * intensity * nDotL;
}
void main()
{
if (bVertexColors[2])
{
FragColor = fColor;
}
else if (bVertexColors[3])
{
FragColor = vec4(fNormal, 1);
}
else if (bVertexColors[4])
{
FragColor = vec4(fTangent, 1);
}
else if (bVertexColors[5])
{
FragColor = vec4(fTexCoords, 0, 1);
}
else
{
int layer = LayerToIndex();
vec3 normals = ComputeNormals(layer);
vec4 diffuse = SamplerToVector(uParameters.Diffuse[layer].Sampler);
vec3 result = uParameters.Diffuse[layer].Color.rgb * diffuse.rgb;
if (uParameters.HasAo)
{
vec3 m = SamplerToVector(uParameters.Ao.Sampler).rgb;
if (uParameters.Ao.HasColorBoost)
{
vec3 color = uParameters.Ao.ColorBoost.Color * uParameters.Ao.ColorBoost.Exponent;
result = mix(result, result * color, m.b);
}
result = mix(result * m.r * uParameters.Ao.AmbientOcclusion, result, m.g);
}
vec4 emissive = SamplerToVector(uParameters.Emissive[layer].Sampler);
result += uParameters.Emissive[layer].Color.rgb * emissive.rgb * uParameters.EmissiveMult;
if (!bVertexColors[1])
{
result += CalcCameraLight(layer, normals);
vec3 lights = vec3(uNumLights > 0 ? 0 : 1);
for (int i = 0; i < uNumLights; i++)
{
float attenuation = 0.0;
float distanceToLight = length(uLights[i].Position - fPos);
if (uLights[i].Type == 0)
{
attenuation = 1.0 / (1.0 + uLights[i].Linear * distanceToLight + uLights[i].Quadratic * pow(distanceToLight, 2));
}
else if (uLights[i].Type == 1)
{
float theta = dot(normalize(uLights[i].Position - fPos), normalize(-vec3(uLights[i].Direction.x, -uLights[i].Attenuation, uLights[i].Direction.y)));
if(theta > uLights[i].ConeAngle)
{
attenuation = 1.0 / (1.0 + uLights[i].Attenuation * pow(distanceToLight, 2));
}
}
vec3 intensity = uLights[i].Color.rgb * uLights[i].Intensity;
lights += result * intensity * attenuation;
}
result *= lights; // use * to darken the scene, + to lighten it
}
result = result / (result + vec3(1.0f));
FragColor = vec4(pow(result, vec3(1.0f / 2.2f)), 1.0f);
}
}

View File

@@ -0,0 +1,52 @@
#version 330 core
layout (location = 1) in vec3 vPos;
layout (location = 2) in vec3 vNormal;
layout (location = 3) in vec3 vTangent;
layout (location = 4) in vec2 vTexCoords;
layout (location = 5) in float vTexLayer;
layout (location = 6) in vec4 vColor;
layout (location = 7) in vec4 vBoneIds;
layout (location = 8) in vec4 vBoneWeights;
layout (location = 9) in mat4 vInstanceMatrix;
layout (location = 13) in vec3 vMorphTarget;
//const int MAX_BONES = 0;
//const int MAX_BONE_INFLUENCE = 0;
uniform mat4 uView;
uniform mat4 uProjection;
uniform float uMorphTime;
//uniform mat4 uFinalBonesMatrix[MAX_BONES];
out vec3 fPos;
out vec3 fNormal;
out vec3 fTangent;
out vec2 fTexCoords;
out float fTexLayer;
out vec4 fColor;
void main()
{
vec4 pos = vec4(mix(vPos, vMorphTarget, uMorphTime), 1.0);
// for(int i = 0 ; i < MAX_BONE_INFLUENCE; i++)
// {
// if(vBoneIds[i] == -1) continue;
// if(vBoneIds[i] >= MAX_BONES)
// {
// break;
// }
//
// vec4 localPos = uFinalBonesMatrix[int(vBoneIds[i])] * pos;
// pos += localPos * vBoneWeights[i];
// }
gl_Position = uProjection * uView * vInstanceMatrix * pos;
fPos = vec3(vInstanceMatrix * pos);
fNormal = mat3(transpose(inverse(vInstanceMatrix))) * vNormal;
fTangent = mat3(transpose(inverse(vInstanceMatrix))) * vTangent;
fTexCoords = vTexCoords;
fTexLayer = vTexLayer;
fColor = vColor;
}

View File

@@ -0,0 +1,12 @@
#version 330 core
in vec2 fTexCoords;
uniform sampler2D screenTexture;
out vec4 FragColor;
void main()
{
FragColor = texture(screenTexture, fTexCoords);
}

View File

@@ -0,0 +1,12 @@
#version 330 core
layout (location = 0) in vec2 vPos;
layout (location = 1) in vec2 vTexCoords;
out vec2 fTexCoords;
void main()
{
gl_Position = vec4(vPos.x, vPos.y, 0.0, 1.0);
fTexCoords = vTexCoords;
}

View File

@@ -0,0 +1,63 @@
#version 430 core
// --------------------- IN ---------------------
in OUT_IN_VARIABLES {
vec3 nearPoint;
vec3 farPoint;
mat4 proj;
mat4 view;
float near;
float far;
} inVar;
// --------------------- OUT --------------------
out vec4 FragColor;
// ------------------- UNIFORM ------------------
uniform vec3 uCamDir;
vec4 grid(vec3 fragPos, float scale) {
vec2 coord = fragPos.xz * scale;
vec2 derivative = fwidth(coord);
vec2 grid = abs(fract(coord - 0.5) - 0.5) / derivative;
float line = min(grid.x, grid.y);
float minimumz = min(derivative.y, 1) * 0.1;
float minimumx = min(derivative.x, 1) * 0.1;
vec4 color = vec4(0.102, 0.102, 0.129, 1.0 - min(line, 1.0));
if(abs(fragPos.x) < minimumx)
color.z = 1.0;
if(abs(fragPos.z) < minimumz)
color.x = 1.0;
return color;
}
float computeDepth(vec3 pos) {
vec4 clip_space_pos = inVar.proj * inVar.view * vec4(pos.xyz, 1.0);
float clip_space_depth = clip_space_pos.z / clip_space_pos.w;
float far = gl_DepthRange.far;
float near = gl_DepthRange.near;
float depth = (((far-near) * clip_space_depth) + near + far) / 2.0;
return depth;
}
float computeLinearDepth(vec3 pos) {
vec4 clip_space_pos = inVar.proj * inVar.view * vec4(pos.xyz, 1.0);
float clip_space_depth = (clip_space_pos.z / clip_space_pos.w) * 2.0 - 1.0;
float linearDepth = (2.0 * inVar.near * inVar.far) / (inVar.far + inVar.near - clip_space_depth * (inVar.far - inVar.near));
return linearDepth / inVar.far;
}
void main() {
float t = -inVar.nearPoint.y / (inVar.farPoint.y - inVar.nearPoint.y);
vec3 fragPos3D = inVar.nearPoint + t * (inVar.farPoint - inVar.nearPoint);
gl_FragDepth = computeDepth(fragPos3D);
float linearDepth = computeLinearDepth(fragPos3D);
float fading = max(0, (0.5 - linearDepth));
FragColor = (grid(fragPos3D, 10) + grid(fragPos3D, 1)) * float(t > 0);
FragColor.a *= fading;
}

View File

@@ -0,0 +1,36 @@
#version 430 core
layout (location = 0) in vec3 vPos;
// --------------------- OUT ---------------------
out OUT_IN_VARIABLES {
vec3 nearPoint;
vec3 farPoint;
mat4 proj;
mat4 view;
float near;
float far;
} outVar;
uniform mat4 proj;
uniform mat4 view;
uniform float uNear;
uniform float uFar;
vec3 UnprojectPoint(vec2 xy, float z) {
mat4 viewInv = inverse(view);
mat4 projInv = inverse(proj);
vec4 unprojectedPoint = viewInv * projInv * vec4(xy, z, 1.0);
return unprojectedPoint.xyz / unprojectedPoint.w;
}
void main()
{
outVar.near = uNear;
outVar.far = uFar;
outVar.proj = proj;
outVar.view = view;
outVar.nearPoint = UnprojectPoint(vPos.xy, -1.0).xyz;
outVar.farPoint = UnprojectPoint(vPos.xy, 1.0).xyz;
gl_Position = vec4(vPos, 1.0f);
}

View File

@@ -0,0 +1,16 @@
#version 330
uniform sampler2D uIcon;
uniform vec4 uColor;
in vec2 fTexCoords;
out vec4 FragColor;
void main()
{
vec4 color = uColor * texture(uIcon, fTexCoords);
if (color.a < 0.1) discard;
FragColor = uColor;
}

View File

@@ -0,0 +1,15 @@
#version 330 core
layout (location = 0) in vec3 vPos;
layout (location = 9) in mat4 vInstanceMatrix;
uniform mat4 uView;
uniform mat4 uProjection;
out vec2 fTexCoords;
void main()
{
gl_Position = uProjection * uView * vInstanceMatrix * vec4(inverse(mat3(uView)) * vPos, 1.0);
fTexCoords = -vPos.xy;
}

BIN
FModel/Resources/nx.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
FModel/Resources/ny.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
FModel/Resources/nz.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -0,0 +1,8 @@
#version 330
out vec4 FragColor;
void main()
{
FragColor = vec4(0.929, 0.588, 0.196, 1.0);
}

View File

@@ -0,0 +1,21 @@
#version 330 core
layout (location = 1) in vec3 vPos;
layout (location = 2) in vec3 vNormal;
layout (location = 9) in mat4 vInstanceMatrix;
layout (location = 13) in vec3 vMorphTarget;
uniform mat4 uView;
uniform vec3 uViewPos;
uniform mat4 uProjection;
uniform float uMorphTime;
void main()
{
vec3 pos = vec3(vInstanceMatrix * vec4(mix(vPos, vMorphTarget, uMorphTime), 1.0));
vec3 nor = mat3(transpose(inverse(vInstanceMatrix))) * vNormal;
float scaleFactor = distance(pos, uViewPos) * 0.0025;
vec3 scaleVertex = pos + nor * scaleFactor;
gl_Position = uProjection * uView * vec4(scaleVertex, 1.0);
}

View File

@@ -0,0 +1,13 @@
#version 330
uniform uint uA;
uniform uint uB;
uniform uint uC;
uniform uint uD;
out uvec4 FragColor;
void main()
{
FragColor = uvec4(uA, uB, uC, uD);
}

View File

@@ -0,0 +1,15 @@
#version 330 core
layout (location = 1) in vec3 vPos;
layout (location = 9) in mat4 vInstanceMatrix;
layout (location = 13) in vec3 vMorphTarget;
uniform mat4 uView;
uniform mat4 uProjection;
uniform float uMorphTime;
void main()
{
vec3 pos = mix(vPos, vMorphTarget, uMorphTime);
gl_Position = uProjection * uView * vInstanceMatrix * vec4(pos, 1.0);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

BIN
FModel/Resources/px.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
FModel/Resources/py.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
FModel/Resources/pz.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1,12 @@
#version 330 core
in vec3 fPos;
uniform samplerCube cubemap;
out vec4 FragColor;
void main()
{
FragColor = texture(cubemap, fPos);
}

View File

@@ -0,0 +1,16 @@
#version 330 core
layout (location = 0) in vec3 vPos;
uniform mat4 uView;
uniform mat4 uProjection;
out vec3 fPos;
void main()
{
fPos = vPos;
vec4 pos = uProjection * uView * vec4(vPos, 1.0);
gl_Position = pos.xyww;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

View File

@@ -285,6 +285,7 @@ namespace FModel.Settings
{FGame.Athena, Constants._NO_PRESET_TRIGGER},
{FGame.PandaGame, Constants._NO_PRESET_TRIGGER},
{FGame.Hotta, Constants._NO_PRESET_TRIGGER},
{FGame.eFootball, Constants._NO_PRESET_TRIGGER}
};
public IDictionary<FGame, string> Presets
{
@@ -295,7 +296,7 @@ namespace FModel.Settings
private IDictionary<FGame, EGame> _overridedGame = new Dictionary<FGame, EGame>
{
{FGame.Unknown, EGame.GAME_UE4_LATEST},
{FGame.FortniteGame, EGame.GAME_UE5_1},
{FGame.FortniteGame, EGame.GAME_UE5_2},
{FGame.ShooterGame, EGame.GAME_Valorant},
{FGame.DeadByDaylight, EGame.GAME_UE4_LATEST},
{FGame.OakGame, EGame.GAME_Borderlands3},
@@ -307,14 +308,15 @@ namespace FModel.Settings
{FGame.Indiana, EGame.GAME_UE4_LATEST},
{FGame.RogueCompany, EGame.GAME_RogueCompany},
{FGame.SwGame, EGame.GAME_UE4_LATEST},
{FGame.Platform, EGame.GAME_UE4_25},
{FGame.Platform, EGame.GAME_UE4_26},
{FGame.BendGame, EGame.GAME_UE4_11},
{FGame.TslGame, EGame.GAME_PlayerUnknownsBattlegrounds},
{FGame.PortalWars, EGame.GAME_UE4_LATEST},
{FGame.Gameface, EGame.GAME_GTATheTrilogyDefinitiveEdition},
{FGame.Athena, EGame.GAME_SeaOfThieves},
{FGame.PandaGame, EGame.GAME_UE4_26},
{FGame.Hotta, EGame.GAME_TowerOfFantasy}
{FGame.Hotta, EGame.GAME_TowerOfFantasy},
{FGame.eFootball, EGame.GAME_UE4_26}
};
public IDictionary<FGame, EGame> OverridedGame
{
@@ -345,6 +347,7 @@ namespace FModel.Settings
{FGame.Athena, null},
{FGame.PandaGame, null},
{FGame.Hotta, null},
{FGame.eFootball, null}
};
public IDictionary<FGame, List<FCustomVersion>> OverridedCustomVersions
{
@@ -375,6 +378,7 @@ namespace FModel.Settings
{FGame.Athena, null},
{FGame.PandaGame, null},
{FGame.Hotta, null},
{FGame.eFootball, null}
};
public IDictionary<FGame, Dictionary<string, bool>> OverridedOptions
{
@@ -411,6 +415,7 @@ namespace FModel.Settings
{FGame.Athena, new FEndpoint[]{new (), new ()}},
{FGame.PandaGame, new FEndpoint[]{new (), new ()}},
{FGame.Hotta, new FEndpoint[]{new (), new ()}},
{FGame.eFootball, new FEndpoint[]{new (), new ()}}
};
public IDictionary<FGame, FEndpoint[]> CustomEndpoints
{
@@ -488,6 +493,7 @@ namespace FModel.Settings
{FGame.Athena, new List<CustomDirectory>()},
{FGame.PandaGame, new List<CustomDirectory>()},
{FGame.Hotta, new List<CustomDirectory>()},
{FGame.eFootball, new List<CustomDirectory>()}
};
public IDictionary<FGame, IList<CustomDirectory>> CustomDirectories
{
@@ -621,6 +627,27 @@ namespace FModel.Settings
set => SetProperty(ref _lodExportFormat, value);
}
private bool _showSkybox = true;
public bool ShowSkybox
{
get => _showSkybox;
set => SetProperty(ref _showSkybox, value);
}
private bool _showGrid = true;
public bool ShowGrid
{
get => _showGrid;
set => SetProperty(ref _showGrid, value);
}
private bool _previewWorlds = true;
public bool PreviewWorlds
{
get => _previewWorlds;
set => SetProperty(ref _previewWorlds, value);
}
private bool _previewStaticMeshes = true;
public bool PreviewStaticMeshes
{

View File

@@ -1,4 +1,4 @@
using System;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using EpicManifestParser.Objects;
@@ -56,6 +56,9 @@ public class EpicApiEndpoint : AbstractApiProvider
private bool IsExpired()
{
if (string.IsNullOrEmpty(UserSettings.Default.LastAuthResponse.AccessToken)) return true;
return DateTime.Now.Subtract(TimeSpan.FromHours(1)) >= UserSettings.Default.LastAuthResponse.ExpiresAt;
var request = new FRestRequest("https://account-public-service-prod.ol.epicgames.com/account/api/oauth/verify");
request.AddHeader("Authorization", $"bearer {UserSettings.Default.LastAuthResponse.AccessToken}");
var response = _client.Get(request);
return response.StatusCode != HttpStatusCode.OK;
}
}

View File

@@ -22,7 +22,6 @@ namespace FModel.ViewModels;
public class ApplicationViewModel : ViewModel
{
private EBuildKind _build;
public EBuildKind Build
{
get => _build;
@@ -33,24 +32,11 @@ public class ApplicationViewModel : ViewModel
}
}
private bool _isReady;
public bool IsReady
{
get => _isReady;
private set => SetProperty(ref _isReady, value);
}
private EStatusKind _status;
public EStatusKind Status
private FStatus _status;
public FStatus Status
{
get => _status;
set
{
SetProperty(ref _status, value);
IsReady = Status != EStatusKind.Loading && Status != EStatusKind.Stopping;
}
set => SetProperty(ref _status, value);
}
public RightClickMenuCommand RightClickMenuCommand => _rightClickMenuCommand ??= new RightClickMenuCommand(this);
@@ -72,12 +58,11 @@ public class ApplicationViewModel : ViewModel
public AesManagerViewModel AesManager { get; }
public AudioPlayerViewModel AudioPlayer { get; }
public MapViewerViewModel MapViewer { get; }
public ModelViewerViewModel ModelViewer { get; }
private OodleCompressor _oodle;
public ApplicationViewModel()
{
Status = EStatusKind.Loading;
Status = new FStatus();
#if DEBUG
Build = EBuildKind.Debug;
#elif RELEASE
@@ -94,8 +79,7 @@ public class ApplicationViewModel : ViewModel
AesManager = new AesManagerViewModel(CUE4Parse);
MapViewer = new MapViewerViewModel(CUE4Parse);
AudioPlayer = new AudioPlayerViewModel();
ModelViewer = new ModelViewerViewModel(CUE4Parse.Game);
Status = EStatusKind.Ready;
Status.SetStatus(EStatusKind.Ready);
}
public void AvoidEmptyGameDirectoryAndSetEGame(bool bAlreadyLaunched)
@@ -201,4 +185,17 @@ public class ApplicationViewModel : ViewModel
(OodleLZ_FuzzSafe) a, (OodleLZ_CheckCRC) b, (OodleLZ_Verbosity) c, d, e, f, g, h, i, (OodleLZ_Decode_ThreadPhase) threadModule);
}
}
}
public async Task InitImGuiSettings()
{
var imgui = Path.Combine(/*UserSettings.Default.OutputDirectory, ".data", */"imgui.ini");
if (File.Exists(imgui)) return;
await ApplicationService.ApiEndpointView.DownloadFileAsync("https://cdn.fmodel.app/d/configurations/imgui.ini", imgui);
if (new FileInfo(imgui).Length == 0)
{
FLogger.AppendError();
FLogger.AppendText("Could not download ImGui settings", Constants.WHITE, true);
}
}
}

View File

@@ -23,6 +23,7 @@ using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Assets.Exports.Wwise;
using CUE4Parse.UE4.IO;
using CUE4Parse.UE4.Localization;
using CUE4Parse.UE4.Objects.Engine;
using CUE4Parse.UE4.Oodle.Objects;
using CUE4Parse.UE4.Readers;
using CUE4Parse.UE4.Shaders;
@@ -38,7 +39,10 @@ using FModel.Services;
using FModel.Settings;
using FModel.Views;
using FModel.Views.Resources.Controls;
using FModel.Views.Snooper;
using Newtonsoft.Json;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
using Serilog;
using SkiaSharp;
@@ -54,7 +58,6 @@ public class CUE4ParseViewModel : ViewModel
RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
private FGame _game;
public FGame Game
{
get => _game;
@@ -68,6 +71,38 @@ public class CUE4ParseViewModel : ViewModel
set => SetProperty(ref _modelIsOverwritingMaterial, value);
}
private bool _modelIsWaitingAnimation;
public bool ModelIsWaitingAnimation
{
get => _modelIsWaitingAnimation;
set => SetProperty(ref _modelIsWaitingAnimation, value);
}
private Snooper _snooper;
public Snooper SnooperViewer
{
get
{
return Application.Current.Dispatcher.Invoke(delegate
{
return _snooper ??= new Snooper(GameWindowSettings.Default,
new NativeWindowSettings
{
Size = new OpenTK.Mathematics.Vector2i(
Convert.ToInt32(SystemParameters.MaximizedPrimaryScreenWidth * .75),
Convert.ToInt32(SystemParameters.MaximizedPrimaryScreenHeight * .85)),
NumberOfSamples = Constants.SAMPLES_COUNT,
WindowBorder = WindowBorder.Resizable,
Flags = ContextFlags.ForwardCompatible,
Profile = ContextProfile.Core,
StartVisible = false,
StartFocused = false,
Title = "3D Viewer"
});
});
}
}
public AbstractVfsFileProvider Provider { get; }
public GameDirectoryViewModel GameDirectory { get; }
public AssetsFolderViewModel AssetsFolder { get; }
@@ -104,6 +139,7 @@ public class CUE4ParseViewModel : ViewModel
default:
{
var parent = gameDirectory.SubstringBeforeLast("\\Content").SubstringAfterLast("\\");
if (gameDirectory.Contains("eFootball")) parent = gameDirectory.SubstringBeforeLast("\\pak").SubstringAfterLast("\\");
Game = Helper.IAmThePanda(parent) ? FGame.PandaGame : parent.ToEnum(FGame.Unknown);
var versions = new VersionContainer(UserSettings.Default.OverridedGame[Game], UserSettings.Default.OverridedPlatform,
customVersions: UserSettings.Default.OverridedCustomVersions[Game],
@@ -128,6 +164,13 @@ public class CUE4ParseViewModel : ViewModel
},
SearchOption.AllDirectories, true, versions);
break;
case FGame.eFootball:
Provider = new DefaultFileProvider(new DirectoryInfo(gameDirectory), new List<DirectoryInfo>
{
new(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + "\\KONAMI\\eFootball\\ST\\Download")
},
SearchOption.AllDirectories, true, versions);
break;
case FGame.Unknown when UserSettings.Default.ManualGames.TryGetValue(gameDirectory, out var settings):
{
versions = new VersionContainer(settings.OverridedGame, UserSettings.Default.OverridedPlatform,
@@ -453,7 +496,7 @@ public class CUE4ParseViewModel : ViewModel
cancellationToken.ThrowIfCancellationRequested();
try
{
Extract(asset.FullPath, TabControl.HasNoTabs);
Extract(cancellationToken, asset.FullPath, TabControl.HasNoTabs);
}
catch
{
@@ -484,7 +527,7 @@ public class CUE4ParseViewModel : ViewModel
cancellationToken.ThrowIfCancellationRequested();
try
{
Extract(asset.FullPath, TabControl.HasNoTabs, true);
Extract(cancellationToken, asset.FullPath, TabControl.HasNoTabs, true);
}
catch
{
@@ -501,11 +544,11 @@ public class CUE4ParseViewModel : ViewModel
{
Thread.Sleep(10);
cancellationToken.ThrowIfCancellationRequested();
Extract(asset.FullPath, TabControl.HasNoTabs);
Extract(cancellationToken, asset.FullPath, TabControl.HasNoTabs);
}
}
public void Extract(string fullPath, bool addNewTab = false, bool bulkSave = false)
public void Extract(CancellationToken cancellationToken, string fullPath, bool addNewTab = false, bool bulkSave = false)
{
Log.Information("User DOUBLE-CLICKED to extract '{FullPath}'", fullPath);
@@ -532,13 +575,13 @@ public class CUE4ParseViewModel : ViewModel
case "uasset":
case "umap":
{
var exports = Provider.LoadObjectExports(fullPath);
var exports = Provider.LoadObjectExports(fullPath); // cancellationToken
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(exports, Formatting.Indented), bulkSave);
if (bulkSave) break;
foreach (var e in exports)
{
if (CheckExport(e))
if (CheckExport(cancellationToken, e))
break;
}
@@ -699,24 +742,24 @@ public class CUE4ParseViewModel : ViewModel
}
}
public void ExtractAndScroll(string fullPath, string objectName)
public void ExtractAndScroll(CancellationToken cancellationToken, string fullPath, string objectName)
{
Log.Information("User CTRL-CLICKED to extract '{FullPath}'", fullPath);
TabControl.AddTab(fullPath.SubstringAfterLast('/'), fullPath.SubstringBeforeLast('/'));
TabControl.SelectedTab.ScrollTrigger = objectName;
var exports = Provider.LoadObjectExports(fullPath);
var exports = Provider.LoadObjectExports(fullPath); // cancellationToken
TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector(""); // json
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(exports, Formatting.Indented), false);
foreach (var e in exports)
{
if (CheckExport(e))
if (CheckExport(cancellationToken, e))
break;
}
}
private bool CheckExport(UObject export) // return true once you wanna stop searching for exports
private bool CheckExport(CancellationToken cancellationToken, UObject export) // return true once you wanna stop searching for exports
{
switch (export)
{
@@ -745,6 +788,7 @@ public class CUE4ParseViewModel : ViewModel
SaveAndPlaySound(Path.Combine(TabControl.SelectedTab.Directory, TabControl.SelectedTab.Header.SubstringBeforeLast('.')).Replace('\\', '/'), audioFormat, data);
return false;
}
case UWorld when UserSettings.Default.PreviewWorlds:
case UStaticMesh when UserSettings.Default.PreviewStaticMeshes:
case USkeletalMesh when UserSettings.Default.PreviewSkeletalMeshes:
case UMaterialInstance when UserSettings.Default.PreviewMaterials && !ModelIsOverwritingMaterial &&
@@ -752,20 +796,20 @@ public class CUE4ParseViewModel : ViewModel
export.Owner.Name.EndsWith($"/RenderSwitch_Materials/{export.Name}", StringComparison.OrdinalIgnoreCase) ||
export.Owner.Name.EndsWith($"/MI_BPTile/{export.Name}", StringComparison.OrdinalIgnoreCase))):
{
Application.Current.Dispatcher.Invoke(delegate
{
var modelViewer = Helper.GetWindow<ModelViewer>("Model Viewer", () => new ModelViewer().Show());
modelViewer.Load(export);
});
if (SnooperViewer.TryLoadExport(cancellationToken, export))
SnooperViewer.Run();
return true;
}
case UMaterialInstance m when ModelIsOverwritingMaterial:
{
Application.Current.Dispatcher.Invoke(delegate
{
var modelViewer = Helper.GetWindow<ModelViewer>("Model Viewer", () => new ModelViewer().Show());
modelViewer.Overwrite(m);
});
SnooperViewer.Renderer.Swap(m);
SnooperViewer.Run();
return true;
}
case UAnimSequence a when ModelIsWaitingAnimation:
{
SnooperViewer.Renderer.Animate(a);
SnooperViewer.Run();
return true;
}
case UStaticMesh when UserSettings.Default.SaveStaticMeshes:
@@ -829,11 +873,11 @@ public class CUE4ParseViewModel : ViewModel
};
var toSave = new Exporter(export, exportOptions);
var toSaveDirectory = new DirectoryInfo(UserSettings.Default.ModelDirectory);
if (toSave.TryWriteToDir(toSaveDirectory, out var savedFileName))
if (toSave.TryWriteToDir(toSaveDirectory, out var label, out var savedFilePath))
{
Log.Information("Successfully saved {FileName}", savedFileName);
Log.Information("Successfully saved {FilePath}", savedFilePath);
FLogger.AppendInformation();
FLogger.AppendText($"Successfully saved {savedFileName}", Constants.WHITE, true);
FLogger.AppendText($"Successfully saved {label}", Constants.WHITE, true);
}
else
{

View File

@@ -105,8 +105,7 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
private void FilterDirectoryFilesToDisplay(CancellationToken cancellationToken, IEnumerable<FileItem> directoryFiles)
{
HashSet<string> filter;
if (directoryFiles == null)
filter = null;
if (directoryFiles == null) filter = null;
else
{
filter = new HashSet<string>();
@@ -132,12 +131,19 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
if (hasFilter)
{
if (filter.Contains(entry.Vfs.Name))
{
entries.Add(entry);
_applicationView.Status.UpdateStatusLabel(entry.Vfs.Name);
}
}
else
{
entries.Add(entry);
_applicationView.Status.UpdateStatusLabel(entry.Vfs.Name);
}
}
_applicationView.Status.UpdateStatusLabel("Folders & Packages");
_applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries);
}
@@ -171,7 +177,8 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
using var archive = new FStreamArchive(fileStream.Name, memoryStream);
var entries = new List<VfsEntry>();
switch (UserSettings.Default.LoadingMode)
var mode = UserSettings.Default.LoadingMode;
switch (mode)
{
case ELoadingMode.AllButNew:
{
@@ -192,6 +199,7 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
entry.Path.EndsWith(".ubulk") || entry.Path.EndsWith(".uptnl")) continue;
entries.Add(entry);
_applicationView.Status.UpdateStatusLabel(entry.Vfs.Name);
}
break;
@@ -215,12 +223,14 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
continue;
entries.Add(entry);
_applicationView.Status.UpdateStatusLabel(entry.Vfs.Name);
}
break;
}
}
_applicationView.Status.UpdateStatusLabel($"{mode.ToString()[6..]} Folders & Packages");
_applicationView.CUE4Parse.AssetsFolder.BulkPopulate(entries);
}
}

View File

@@ -35,6 +35,9 @@ public class MenuCommand : ViewModelCommand<ApplicationViewModel>
contextViewModel.CUE4Parse.TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("json");
contextViewModel.CUE4Parse.TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(contextViewModel.CUE4Parse.GameDirectory.DirectoryFiles, Formatting.Indented), false);
break;
case "Views_3dViewer":
contextViewModel.CUE4Parse.SnooperViewer.Run();
break;
case "Views_AudioPlayer":
Helper.OpenWindow<AdonisWindow>("Audio Player", () => new AudioPlayer().Show());
break;
@@ -109,4 +112,4 @@ public class MenuCommand : ViewModelCommand<ApplicationViewModel>
cancellationToken.ThrowIfCancellationRequested();
foreach (var f in parent.Folders) LoopFolders(cancellationToken, f, isExpanded);
}
}
}

View File

@@ -29,7 +29,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
foreach (var asset in assetItems)
{
cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.Extract(asset.FullPath, true);
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, true);
}
break;
@@ -45,7 +45,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
foreach (var asset in assetItems)
{
cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.Extract(asset.FullPath);
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath);
contextViewModel.CUE4Parse.TabControl.SelectedTab.SaveProperty(false);
}
@@ -54,7 +54,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
foreach (var asset in assetItems)
{
cancellationToken.ThrowIfCancellationRequested();
contextViewModel.CUE4Parse.Extract(asset.FullPath);
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath);
contextViewModel.CUE4Parse.TabControl.SelectedTab.SaveImage(false);
}
@@ -62,4 +62,4 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
}
});
}
}
}

View File

@@ -117,6 +117,7 @@ public class GameSelectorViewModel : ViewModel
yield return GetSteamGame(578080, "\\TslGame\\Content\\Paks"); // PUBG
yield return GetSteamGame(677620, "\\PortalWars\\Content\\Paks"); // Splitgate
yield return GetSteamGame(1172620, "\\Athena\\Content\\Paks"); // Sea of Thieves
yield return GetSteamGame(1665460, "\\pak"); // eFootball 2023
yield return GetRockstarGamesGame("GTA III - Definitive Edition", "\\Gameface\\Content\\Paks");
yield return GetRockstarGamesGame("GTA San Andreas - Definitive Edition", "\\Gameface\\Content\\Paks");
yield return GetRockstarGamesGame("GTA Vice City - Definitive Edition", "\\Gameface\\Content\\Paks");
@@ -337,22 +338,14 @@ public class GameSelectorViewModel : ViewModel
dic[key] = val;
}
if (dic.Keys.Count <= 0) return null;
AppInfo appInfo = new();
var appId = dic["appid"];
var name = dic["name"];
var installDir = dic["installDir"];
if (!dic.TryGetValue("appid", out var appId) ||
!dic.TryGetValue("name", out var name) ||
!dic.TryGetValue("installDir", out var installDir)) return null;
var path = Path.GetDirectoryName(appMetaFile);
var libGameRoot = Path.Combine(path, "common", installDir);
if (!Directory.Exists(libGameRoot)) return null;
appInfo.Id = appId;
appInfo.Name = name;
appInfo.GameRoot = libGameRoot;
return appInfo;
return !Directory.Exists(libGameRoot) ? null : new AppInfo { Id = appId, Name = name, GameRoot = libGameRoot };
}
private static List<string> GetSteamLibs()

View File

@@ -175,7 +175,7 @@ public class MapViewerViewModel : ViewModel
}
private const int _widthHeight = 2048;
private const int _brRadius = 131000;
private const int _brRadius = 141000;
private const int _prRadius = 51000;
private int _mapIndex;
public int MapIndex // 0 is BR, 1 is PR
@@ -368,8 +368,9 @@ public class MapViewerViewModel : ViewModel
private FVector2D GetMapPosition(FVector vector, int mapRadius)
{
var nx = (vector.Y + mapRadius) / (mapRadius * 2) * _widthHeight;
var ny = (1 - (vector.X + mapRadius) / (mapRadius * 2)) * _widthHeight;
const int wh = 2048 + 128 + 32;
var nx = (vector.Y + mapRadius) / (mapRadius * 2) * wh;
var ny = (1 - (vector.X + mapRadius) / (mapRadius * 2)) * wh;
return new FVector2D(nx, ny);
}

View File

@@ -1,768 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media.Media3D;
using CUE4Parse_Conversion;
using CUE4Parse.UE4.Assets;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.Utils;
using CUE4Parse_Conversion.Materials;
using CUE4Parse_Conversion.Meshes;
using CUE4Parse_Conversion.Meshes.glTF;
using CUE4Parse_Conversion.Meshes.PSK;
using CUE4Parse_Conversion.Textures;
using FModel.Framework;
using FModel.Services;
using FModel.Settings;
using FModel.Views.Resources.Controls;
using HelixToolkit.SharpDX.Core;
using HelixToolkit.Wpf.SharpDX;
using Ookii.Dialogs.Wpf;
using Serilog;
using SharpDX;
using SharpGLTF.Geometry;
using SharpGLTF.Geometry.VertexTypes;
using SharpGLTF.Scenes;
using SharpGLTF.Schema2;
using SharpGLTF.Transforms;
using SkiaSharp;
using Camera = HelixToolkit.Wpf.SharpDX.Camera;
using Exporter = CUE4Parse_Conversion.Exporter;
using Geometry3D = HelixToolkit.SharpDX.Core.Geometry3D;
using PerspectiveCamera = HelixToolkit.Wpf.SharpDX.PerspectiveCamera;
using Vector2 = SharpDX.Vector2;
using Vector3 = SharpDX.Vector3;
using VERTEX = SharpGLTF.Geometry.VertexTypes.VertexPositionNormalTangent;
namespace FModel.ViewModels;
public class ModelViewerViewModel : ViewModel
{
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
private EffectsManager _effectManager;
public EffectsManager EffectManager
{
get => _effectManager;
set => SetProperty(ref _effectManager, value);
}
private Camera _cam;
public Camera Cam
{
get => _cam;
set => SetProperty(ref _cam, value);
}
private ModelAndCam _selectedModel; // selected mesh
public ModelAndCam SelectedModel
{
get => _selectedModel;
set => SetProperty(ref _selectedModel, value);
}
private readonly ObservableCollection<ModelAndCam> _loadedModels; // mesh list
public ICollectionView LoadedModelsView { get; }
private bool _appendMode;
public bool AppendMode
{
get => _appendMode;
set => SetProperty(ref _appendMode, value);
}
public bool CanAppend => SelectedModel != null;
public TextureModel HDRi { get; private set; }
private readonly FGame _game;
private readonly int[] _facesIndex = { 1, 0, 2 };
public ModelViewerViewModel(FGame game)
{
_game = game;
_loadedModels = new ObservableCollection<ModelAndCam>();
EffectManager = new DefaultEffectsManager();
LoadedModelsView = new ListCollectionView(_loadedModels);
Cam = new PerspectiveCamera { NearPlaneDistance = 0.1, FarPlaneDistance = double.PositiveInfinity, FieldOfView = 90 };
LoadHDRi();
}
private void LoadHDRi()
{
var cubeMap = Application.GetResourceStream(new Uri("/FModel;component/Resources/approaching_storm_cubemap.dds", UriKind.Relative));
HDRi = TextureModel.Create(cubeMap?.Stream);
}
public void LoadExport(UObject export)
{
#if DEBUG
LoadHDRi();
#endif
ModelAndCam p;
if (AppendMode && CanAppend)
{
p = SelectedModel;
_loadedModels.Add(new ModelAndCam(export) { IsVisible = false });
}
else
{
p = new ModelAndCam(export);
_loadedModels.Add(p);
}
switch (export)
{
case UStaticMesh st:
LoadStaticMesh(st, p);
break;
case USkeletalMesh sk:
LoadSkeletalMesh(sk, p);
break;
case UMaterialInstance mi:
LoadMaterialInstance(mi, p);
break;
default:
throw new ArgumentOutOfRangeException(nameof(export));
}
if (AppendMode && CanAppend) return;
SelectedModel = p;
Cam.UpDirection = new Vector3D(0, 1, 0);
Cam.Position = p.Position;
Cam.LookDirection = p.LookDirection;
}
#region PUBLIC METHODS
public void RenderingToggle()
{
if (SelectedModel == null) return;
SelectedModel.RenderingToggle = !SelectedModel.RenderingToggle;
}
public void WirefreameToggle()
{
if (SelectedModel == null) return;
SelectedModel.WireframeToggle = !SelectedModel.WireframeToggle;
}
public void MaterialColorToggle()
{
if (SelectedModel == null) return;
SelectedModel.ShowMaterialColor = !SelectedModel.ShowMaterialColor;
}
public void DiffuseOnlyToggle()
{
if (SelectedModel == null) return;
SelectedModel.DiffuseOnlyToggle = !SelectedModel.DiffuseOnlyToggle;
}
public void FocusOnSelectedMesh()
{
Cam.AnimateTo(SelectedModel.Position, SelectedModel.LookDirection, new Vector3D(0, 1, 0), 500);
}
public async Task SaveLoadedModels()
{
if (_loadedModels.Count < 1) return;
var folderBrowser = new VistaFolderBrowserDialog { ShowNewFolderButton = true };
if (folderBrowser.ShowDialog() == false) return;
await _threadWorkerView.Begin(_ =>
{
var exportOptions = new ExporterOptions
{
TextureFormat = UserSettings.Default.TextureExportFormat,
LodFormat = UserSettings.Default.LodExportFormat,
MeshFormat = UserSettings.Default.MeshExportFormat,
SocketFormat = UserSettings.Default.SocketExportFormat,
Platform = UserSettings.Default.OverridedPlatform,
ExportMorphTargets = UserSettings.Default.SaveMorphTargets
};
foreach (var model in _loadedModels)
{
var toSave = new Exporter(model.Export, exportOptions);
if (toSave.TryWriteToDir(new DirectoryInfo(folderBrowser.SelectedPath), out var savedFileName))
{
Log.Information("Successfully saved {FileName}", savedFileName);
FLogger.AppendInformation();
FLogger.AppendText($"Successfully saved {savedFileName}", Constants.WHITE, true);
}
else
{
Log.Error("{FileName} could not be saved", savedFileName);
FLogger.AppendError();
FLogger.AppendText($"Could not save '{savedFileName}'", Constants.WHITE, true);
}
}
});
}
public void SaveAsScene()
{
if (_loadedModels.Count < 1) return;
var fileBrowser = new VistaSaveFileDialog
{
Title = "Save Loaded Models As...",
DefaultExt = ".glb",
Filter = "glTF Binary File (*.glb)|*.glb|glTF ASCII File (*.gltf)|*.gltf|All Files(*.*)|*.*",
AddExtension = true,
OverwritePrompt = true,
CheckPathExists = true
};
if (fileBrowser.ShowDialog() == false || string.IsNullOrEmpty(fileBrowser.FileName)) return;
var sceneBuilder = new SceneBuilder();
var materialExports = new List<MaterialExporter>();
foreach (var model in _loadedModels)
{
switch (model.Export)
{
case UStaticMesh sm:
{
var mesh = new MeshBuilder<VERTEX, VertexColorXTextureX, VertexEmpty>(sm.Name);
if (sm.TryConvert(out var convertedMesh) && convertedMesh.LODs.Count > 0)
{
var lod = convertedMesh.LODs.First();
for (var i = 0; i < lod.Sections.Value.Length; i++)
{
Gltf.ExportStaticMeshSections(i, lod, lod.Sections.Value[i], materialExports, mesh);
}
sceneBuilder.AddRigidMesh(mesh, AffineTransform.Identity);
}
break;
}
case USkeletalMesh sk:
{
var mesh = new MeshBuilder<VERTEX, VertexColorXTextureX, VertexJoints4>(sk.Name);
if (sk.TryConvert(out var convertedMesh) && convertedMesh.LODs.Count > 0)
{
var lod = convertedMesh.LODs.First();
for (var i = 0; i < lod.Sections.Value.Length; i++)
{
Gltf.ExportSkelMeshSections(i, lod, lod.Sections.Value[i], materialExports, mesh);
}
var armatureNodeBuilder = new NodeBuilder(sk.Name + ".ao");
var armature = Gltf.CreateGltfSkeleton(convertedMesh.RefSkeleton, armatureNodeBuilder);
sceneBuilder.AddSkinnedMesh(mesh, Matrix4x4.Identity, armature);
}
break;
}
}
}
var scene = sceneBuilder.ToGltf2();
var fileName = fileBrowser.FileName;
if (fileName.EndsWith(".glb", StringComparison.OrdinalIgnoreCase))
scene.SaveGLB(fileName);
else if (fileName.EndsWith(".gltf", StringComparison.OrdinalIgnoreCase))
scene.SaveGLTF(fileName);
else if (fileName.EndsWith(".obj", StringComparison.OrdinalIgnoreCase))
scene.SaveAsWavefront(fileName);
else
throw new ArgumentOutOfRangeException(nameof(fileName), $@"Unknown file format {fileName.SubstringAfterWithLast('.')}");
if (!CheckIfSaved(fileName)) return;
foreach (var materialExport in materialExports)
{
materialExport.TryWriteToDir(new DirectoryInfo(StringUtils.SubstringBeforeWithLast(fileName, '\\')), out _);
}
}
public void CopySelectedMaterialName()
{
if (SelectedModel is not { } m || m.SelectedGeometry is null)
return;
Clipboard.SetText(m.SelectedGeometry.DisplayName.TrimEnd());
}
#endregion
public bool TryOverwriteMaterial(UMaterialInstance materialInstance)
{
if (SelectedModel?.SelectedGeometry == null || _loadedModels.Count < 1) return false;
var (m, _, _) = LoadMaterial(materialInstance);
var obj = new ResolvedLoadedObject(materialInstance);
switch (_loadedModels[SelectedModel.SelectedGeometry.ExportIndex].Export)
{
case UStaticMesh { Materials: { } } st:
st.Materials[SelectedModel.SelectedGeometry.MaterialIndex] = obj;
break;
case USkeletalMesh sk:
sk.SkeletalMaterials[SelectedModel.SelectedGeometry.MaterialIndex].Material = obj;
break;
case UMaterialInstance:
SelectedModel.SwapExport(materialInstance);
break;
}
SelectedModel.SelectedGeometry.Material = m;
return m != null;
}
private void LoadMaterialInstance(UMaterialInstance materialInstance, ModelAndCam cam)
{
var builder = new MeshBuilder();
builder.AddBox(Vector3.Zero, 10, 10, 10);
cam.TriangleCount = 12; // no need to count
SetupCameraAndAxis(new FBox(new FVector(-8), new FVector(8)), cam);
var (m, isRendering, isTransparent) = LoadMaterial(materialInstance);
cam.Group3d.Add(new CustomMeshGeometryModel3D
{
Transform = new RotateTransform3D(new AxisAngleRotation3D(new Vector3D(0, 1, 0), -45)),
DisplayName = materialInstance.Name, Geometry = builder.ToMeshGeometry3D(), MaterialIndex = 0,
Material = m, IsTransparent = isTransparent, IsRendering = isRendering, ExportIndex = _loadedModels.Count - 1
});
}
private void LoadStaticMesh(UStaticMesh mesh, ModelAndCam cam)
{
if (!mesh.TryConvert(out var convertedMesh) || convertedMesh.LODs.Count <= 0)
{
return;
}
SetupCameraAndAxis(convertedMesh.BoundingBox, cam);
foreach (var lod in convertedMesh.LODs.Where(lod => !lod.SkipLod))
{
PushLod(lod.Sections.Value, lod.Verts, lod.Indices.Value, cam);
break;
}
}
private void LoadSkeletalMesh(USkeletalMesh mesh, ModelAndCam cam)
{
if (!mesh.TryConvert(out var convertedMesh) || convertedMesh.LODs.Count <= 0)
{
return;
}
SetupCameraAndAxis(convertedMesh.BoundingBox, cam);
foreach (var lod in convertedMesh.LODs.Where(lod => !lod.SkipLod))
{
PushLod(lod.Sections.Value, lod.Verts, lod.Indices.Value, cam);
break;
}
}
private void PushLod(CMeshSection[] sections, CMeshVertex[] verts, FRawStaticIndexBuffer indices, ModelAndCam cam)
{
for (var i = 0; i < sections.Length; i++) // each section is a mesh part with its own material
{
var section = sections[i];
var builder = new MeshBuilder();
cam.TriangleCount += section.NumFaces; // NumFaces * 3 (triangle) = next section FirstIndex
for (var j = 0; j < section.NumFaces; j++) // draw a triangle for each face
{
foreach (var t in _facesIndex) // triangle face 1 then 0 then 2
{
var id = section.FirstIndex + j * 3 + t;
var vert = verts[indices[id]];
var p = new Vector3(vert.Position.X, vert.Position.Z, vert.Position.Y); // up direction is Y
var n = new Vector3(vert.Normal.X, vert.Normal.Z, vert.Normal.Y);
n.Normalize();
builder.AddNode(p, n, new Vector2(vert.UV.U, vert.UV.V));
builder.TriangleIndices.Add(j * 3 + t); // one mesh part is "j * 3 + t" use "id" if you're building the full mesh
}
}
if (section.Material == null || !section.Material.TryLoad(out var o) || o is not UMaterialInterface material)
{
cam.Group3d.Add(new CustomMeshGeometryModel3D
{
DisplayName = section.Material?.Name.ToString() ?? $"material_{section.MaterialIndex}", MaterialIndex = section.MaterialIndex,
Geometry = builder.ToMeshGeometry3D(), Material = new PBRMaterial(), IsTransparent = false,
IsRendering = true, ExportIndex = _loadedModels.Count - 1
});
}
else
{
var (m, isRendering, isTransparent) = LoadMaterial(material);
cam.Group3d.Add(new CustomMeshGeometryModel3D
{
DisplayName = material.Name, MaterialIndex = section.MaterialIndex,
Geometry = builder.ToMeshGeometry3D(), Material = m, IsTransparent = isTransparent,
IsRendering = isRendering, ExportIndex = _loadedModels.Count - 1
});
}
}
}
private (PBRMaterial material, bool isRendering, bool isTransparent) LoadMaterial(UMaterialInterface unrealMaterial)
{
var m = new PBRMaterial { RenderShadowMap = true, EnableAutoTangent = true, RenderEnvironmentMap = true };
var parameters = new CMaterialParams();
unrealMaterial.GetParams(parameters);
var isRendering = !parameters.IsNull;
if (isRendering)
{
if (!parameters.HasTopDiffuseTexture && parameters.DiffuseColor is { A: > 0 } diffuseColor)
{
m.AlbedoColor = new Color4(diffuseColor.R, diffuseColor.G, diffuseColor.B, diffuseColor.A);
}
else if (parameters.Diffuse is UTexture2D diffuse)
{
m.AlbedoMap = new TextureModel(diffuse.Decode(UserSettings.Default.OverridedPlatform)?.Encode(SKEncodedImageFormat.Png, 100).AsStream());
}
if (parameters.Normal is UTexture2D normal)
{
m.NormalMap = new TextureModel(normal.Decode(UserSettings.Default.OverridedPlatform)?.Encode(SKEncodedImageFormat.Png, 100).AsStream());
}
if (parameters.Specular is UTexture2D specular)
{
var mip = specular.GetFirstMip();
var platform = UserSettings.Default.OverridedPlatform;
TextureDecoder.DecodeTexture(mip, specular.Format, specular.isNormalMap, platform, out var data, out var colorType);
switch (_game)
{
case FGame.FortniteGame:
{
// Fortnite's Specular Texture Channels
// R Specular
// G Metallic
// B Roughness
unsafe
{
var offset = 0;
fixed (byte* d = data)
{
for (var i = 0; i < mip.SizeX * mip.SizeY; i++)
{
d[offset] = 0;
(d[offset + 1], d[offset + 2]) = (d[offset + 2], d[offset + 1]); // swap G and B
offset += 4;
}
}
}
parameters.RoughnessValue = 1;
parameters.MetallicValue = 1;
break;
}
case FGame.ShooterGame:
{
var packedPBRType = specular.Name[(specular.Name.LastIndexOf('_') + 1)..];
switch (packedPBRType)
{
case "MRAE": // R: Metallic, G: AO (0-127) & Emissive (128-255), B: Roughness (Character PBR)
unsafe
{
var offset = 0;
fixed (byte* d = data)
{
for (var i = 0; i < mip.SizeX * mip.SizeY; i++)
{
(d[offset], d[offset + 2]) = (d[offset + 2], d[offset]); // swap R and B
(d[offset], d[offset + 1]) = (d[offset + 1], d[offset]); // swap R and G
offset += 4;
}
}
}
break;
case "MRAS": // R: Metallic, B: Roughness, B: AO, A: Specular (Legacy PBR)
case "MRA": // R: Metallic, B: Roughness, B: AO (Environment PBR)
case "MRS": // R: Metallic, G: Roughness, B: Specular (Weapon PBR)
unsafe
{
var offset = 0;
fixed (byte* d = data)
{
for (var i = 0; i < mip.SizeX * mip.SizeY; i++)
{
(d[offset], d[offset + 2]) = (d[offset + 2], d[offset]); // swap R and B
offset += 4;
}
}
}
break;
}
parameters.RoughnessValue = 1;
parameters.MetallicValue = 1;
break;
}
case FGame.Gameface:
{
// GTA's Specular Texture Channels
// R Metallic
// G Roughness
// B Specular
unsafe
{
var offset = 0;
fixed (byte* d = data)
{
for (var i = 0; i < mip.SizeX * mip.SizeY; i++)
{
(d[offset], d[offset + 2]) = (d[offset + 2], d[offset]); // swap R and B
offset += 4;
}
}
}
break;
}
}
using var bitmap = new SKBitmap(new SKImageInfo(mip.SizeX, mip.SizeY, colorType, SKAlphaType.Unpremul));
unsafe
{
fixed (byte* p = data)
{
bitmap.SetPixels(new IntPtr(p));
}
}
// R -> AO G -> Roughness B -> Metallic
m.RoughnessMetallicMap = new TextureModel(bitmap.Encode(SKEncodedImageFormat.Png, 100).AsStream());
m.RoughnessFactor = parameters.RoughnessValue;
m.MetallicFactor = parameters.MetallicValue;
m.RenderAmbientOcclusionMap = parameters.SpecularValue > 0;
}
if (parameters.HasTopEmissiveTexture && parameters.Emissive is UTexture2D emissive && parameters.EmissiveColor is { A: > 0 } emissiveColor)
{
m.EmissiveColor = new Color4(emissiveColor.R, emissiveColor.G, emissiveColor.B, emissiveColor.A);
m.EmissiveMap = new TextureModel(emissive.Decode(UserSettings.Default.OverridedPlatform)?.Encode(SKEncodedImageFormat.Png, 100).AsStream());
}
}
else
{
m.AlbedoColor = new Color4(1, 0, 0, 1);
}
return (m, isRendering, parameters.IsTransparent);
}
private void SetupCameraAndAxis(FBox box, ModelAndCam cam)
{
if (AppendMode && CanAppend) return;
var center = box.GetCenter();
var lineBuilder = new LineBuilder();
lineBuilder.AddLine(new Vector3(box.Min.X, center.Z, center.Y), new Vector3(box.Max.X, center.Z, center.Y));
cam.XAxis = lineBuilder.ToLineGeometry3D();
lineBuilder = new LineBuilder();
lineBuilder.AddLine(new Vector3(center.X, box.Min.Z, center.Y), new Vector3(center.X, box.Max.Z, center.Y));
cam.YAxis = lineBuilder.ToLineGeometry3D();
lineBuilder = new LineBuilder();
lineBuilder.AddLine(new Vector3(center.X, center.Z, box.Min.Y), new Vector3(center.X, center.Z, box.Max.Y));
cam.ZAxis = lineBuilder.ToLineGeometry3D();
cam.Position = new Point3D(box.Max.X + center.X * 2, center.Z, box.Min.Y + center.Y * 2);
cam.LookDirection = new Vector3D(-cam.Position.X + center.X, 0, -cam.Position.Z + center.Y);
}
private bool CheckIfSaved(string path)
{
if (File.Exists(path))
{
Log.Information("Successfully saved {FileName}", path);
FLogger.AppendInformation();
FLogger.AppendText($"Successfully saved {path}", Constants.WHITE, true);
return true;
}
Log.Error("{FileName} could not be saved", path);
FLogger.AppendError();
FLogger.AppendText($"Could not save '{path}'", Constants.WHITE, true);
return false;
}
public void Clear()
{
foreach (var g in _loadedModels.ToList())
{
g.Dispose();
_loadedModels.Remove(g);
}
}
}
public class ModelAndCam : ViewModel
{
public UObject Export { get; private set; }
public Point3D Position { get; set; }
public Vector3D LookDirection { get; set; }
public Geometry3D XAxis { get; set; }
public Geometry3D YAxis { get; set; }
public Geometry3D ZAxis { get; set; }
public int TriangleCount { get; set; }
private bool _isVisible = true;
public bool IsVisible
{
get => _isVisible;
set => SetProperty(ref _isVisible, value);
}
private bool _renderingToggle;
public bool RenderingToggle
{
get => _renderingToggle;
set
{
SetProperty(ref _renderingToggle, value);
foreach (var g in Group3d)
{
if (g is not CustomMeshGeometryModel3D geometryModel)
continue;
geometryModel.IsRendering = !geometryModel.IsRendering;
}
}
}
private bool _wireframeToggle;
public bool WireframeToggle
{
get => _wireframeToggle;
set
{
SetProperty(ref _wireframeToggle, value);
foreach (var g in Group3d)
{
if (g is not CustomMeshGeometryModel3D geometryModel)
continue;
geometryModel.RenderWireframe = !geometryModel.RenderWireframe;
}
}
}
private bool _showMaterialColor;
public bool ShowMaterialColor
{
get => _showMaterialColor;
set
{
SetProperty(ref _showMaterialColor, value);
for (var i = 0; i < Group3d.Count; i++)
{
if (Group3d[i] is not CustomMeshGeometryModel3D { Material: PBRMaterial material } m)
continue;
var index = B(i);
material.RenderAlbedoMap = !_showMaterialColor;
if (_showMaterialColor)
{
m.Tag = material.AlbedoColor;
material.AlbedoColor = new Color4(_table[C(index)] / 255, _table[C(index >> 1)] / 255, _table[C(index >> 2)] / 255, 1);
}
else material.AlbedoColor = (Color4) m.Tag;
}
}
}
private bool _diffuseOnlyToggle;
public bool DiffuseOnlyToggle
{
get => _diffuseOnlyToggle;
set
{
SetProperty(ref _diffuseOnlyToggle, value);
foreach (var g in Group3d)
{
if (g is not CustomMeshGeometryModel3D { Material: PBRMaterial material })
continue;
material.RenderAmbientOcclusionMap = !material.RenderAmbientOcclusionMap;
material.RenderDisplacementMap = !material.RenderDisplacementMap;
// material.RenderEmissiveMap = !material.RenderEmissiveMap;
// material.RenderEnvironmentMap = !material.RenderEnvironmentMap;
material.RenderIrradianceMap = !material.RenderIrradianceMap;
material.RenderRoughnessMetallicMap = !material.RenderRoughnessMetallicMap;
material.RenderShadowMap = !material.RenderShadowMap;
material.RenderNormalMap = !material.RenderNormalMap;
}
}
}
private CustomMeshGeometryModel3D _selectedGeometry; // selected material
public CustomMeshGeometryModel3D SelectedGeometry
{
get => _selectedGeometry;
set => SetProperty(ref _selectedGeometry, value);
}
private ObservableElement3DCollection _group3d; // material list
public ObservableElement3DCollection Group3d
{
get => _group3d;
set => SetProperty(ref _group3d, value);
}
private readonly float[] _table = { 255 * 0.9f, 25 * 3.0f, 255 * 0.6f, 255 * 0.0f };
private readonly int[] _table2 = { 0, 1, 2, 4, 7, 3, 5, 6 };
public ModelAndCam(UObject export)
{
Export = export;
TriangleCount = 0;
Group3d = new ObservableElement3DCollection();
}
private int B(int x) => (x & 0xFFF8) | _table2[x & 7] ^ 7;
private int C(int x) => (x & 1) | ((x >> 2) & 2);
public void SwapExport(UObject e)
{
Export = e;
}
public void Dispose()
{
TriangleCount = 0;
SelectedGeometry = null;
foreach (var g in Group3d.ToList())
{
g.Dispose();
Group3d.Remove(g);
}
}
}
public class CustomMeshGeometryModel3D : MeshGeometryModel3D
{
public string DisplayName { get; set; }
public int MaterialIndex { get; set; }
public int ExportIndex { get; set; }
}

View File

@@ -1,5 +1,4 @@
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using FModel.Framework;
@@ -53,7 +52,7 @@ public class ThreadWorkerViewModel : ViewModel
public async Task Begin(Action<CancellationToken> action)
{
if (!_applicationView.IsReady)
if (!_applicationView.Status.IsReady)
{
SignalOperationInProgress();
return;
@@ -79,7 +78,7 @@ public class ThreadWorkerViewModel : ViewModel
{
if (_jobs.Count > 0)
{
_applicationView.Status = EStatusKind.Loading;
_applicationView.Status.SetStatus(EStatusKind.Loading);
await foreach (var job in _jobs)
{
try
@@ -89,7 +88,7 @@ public class ThreadWorkerViewModel : ViewModel
}
catch (OperationCanceledException)
{
_applicationView.Status = EStatusKind.Stopped;
_applicationView.Status.SetStatus(EStatusKind.Stopped);
CurrentCancellationTokenSource = null; // kill token
OperationCancelled = true;
OperationCancelled = false;
@@ -97,7 +96,7 @@ public class ThreadWorkerViewModel : ViewModel
}
catch (Exception e)
{
_applicationView.Status = EStatusKind.Failed;
_applicationView.Status.SetStatus(EStatusKind.Failed);
CurrentCancellationTokenSource = null; // kill token
Log.Error("{Exception}", e);
@@ -134,7 +133,7 @@ public class ThreadWorkerViewModel : ViewModel
}
}
_applicationView.Status = EStatusKind.Completed;
_applicationView.Status.SetStatus(EStatusKind.Completed);
CurrentCancellationTokenSource = null; // kill token
}
}

View File

@@ -24,41 +24,41 @@
<DataTemplate x:Key="BrTemplate">
<StackPanel VerticalAlignment="Center" Margin="25 0">
<CheckBox Content="Points Of Interest" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.BrPois}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding IsReady}" />
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" />
<CheckBox Content="Landmarks" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.BrLandmarks}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding IsReady}" />
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" />
<!-- <CheckBox Content="Tags Location" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.BrTagsLocation}" -->
<!-- DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding IsReady}" /> -->
<CheckBox Content="Patrols Path" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.BrPatrolsPath}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding IsReady}" />
<CheckBox Content="Upgrade Benches" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.BrUpgradeBenches}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding IsReady}" />
<!-- DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" /> -->
<!-- <CheckBox Content="Patrols Path" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.BrPatrolsPath}" -->
<!-- DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" /> -->
<!-- <CheckBox Content="Upgrade Benches" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.BrUpgradeBenches}" -->
<!-- DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" /> -->
<!-- <CheckBox Content="Phonebooths" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.BrPhonebooths}" -->
<!-- DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding IsReady}" /> -->
<CheckBox Content="Weapon-o-matic/Mending Machines" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.BrVendingMachines}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding IsReady}" />
<CheckBox Content="Bounty Boards" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.BrBountyBoards}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding IsReady}" />
<!-- DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" /> -->
<!-- <CheckBox Content="Weapon-o-matic/Mending Machines" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.BrVendingMachines}" -->
<!-- DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" /> -->
<!-- <CheckBox Content="Bounty Boards" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.BrBountyBoards}" -->
<!-- DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" /> -->
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="PrTemplate">
<StackPanel VerticalAlignment="Center" Margin="25 0">
<CheckBox Content="Landmarks" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.PrLandmarks}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding IsReady}" />
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" />
<CheckBox Content="Cannonball" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.PrCannonball}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding IsReady}" />
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" />
<CheckBox Content="Skydive" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.PrSkydive}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding IsReady}" />
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" />
<CheckBox Content="Shooting Targets" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.PrShootingTargets}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding IsReady}" />
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" />
<CheckBox Content="Parkour" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.PrParkour}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding IsReady}" />
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" />
<CheckBox Content="Time Trials" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.PrTimeTrials}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding IsReady}" />
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" />
<CheckBox Content="Vending Machines" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.PrVendingMachines}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding IsReady}" />
<CheckBox Content="Music Blocks" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.PrMusicBlocks}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding IsReady}" />
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" />
<!-- <CheckBox Content="Music Blocks" Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" IsChecked="{Binding MapViewer.PrMusicBlocks}" -->
<!-- DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.MapViewer}}}" IsEnabled="{Binding Status.IsReady}" /> -->
</StackPanel>
</DataTemplate>
</ResourceDictionary>
@@ -108,7 +108,7 @@
<ContentControl ContentTemplateSelector="{StaticResource TagTemplateSelector}" Content="{Binding SelectedItem.Tag, ElementName=MapTree}" />
</Grid>
<Button Grid.Row="2" Content="Save Image" Margin="5" IsEnabled="{Binding IsReady}" VerticalAlignment="Bottom" Click="OnClick" />
<Button Grid.Row="2" Content="Save Image" Margin="5" IsEnabled="{Binding Status.IsReady}" VerticalAlignment="Bottom" Click="OnClick" />
</Grid>
<Grid Grid.Column="1" HorizontalAlignment="Stretch">
@@ -118,9 +118,9 @@
<TextBlock Text="Minimap is loading, please wait..." HorizontalAlignment="Center" VerticalAlignment="Center"/>
<Image UseLayoutRounding="True" Source="{Binding MapViewer.MapImage}" HorizontalAlignment="Right" VerticalAlignment="Center"
Visibility="{Binding IsReady, Converter={StaticResource BoolToVisibilityConverter}}" />
Visibility="{Binding Status.IsReady, Converter={StaticResource BoolToVisibilityConverter}}" />
<Image UseLayoutRounding="True" Source="{Binding MapViewer.LayerImage}" HorizontalAlignment="Right" VerticalAlignment="Center"
Visibility="{Binding IsReady, Converter={StaticResource BoolToVisibilityConverter}}" />
Visibility="{Binding Status.IsReady, Converter={StaticResource BoolToVisibilityConverter}}" />
</Grid>
</Grid>
</adonisControls:AdonisWindow>

View File

@@ -1,135 +0,0 @@
<adonisControls:AdonisWindow x:Class="FModel.Views.ModelViewer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI"
xmlns:helix="http://helix-toolkit.org/wpf/SharpDX"
WindowStartupLocation="CenterScreen" ResizeMode="CanResize" IconVisibility="Collapsed"
PreviewKeyDown="OnWindowKeyDown" Closing="OnClosing"
Height="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenHeight}, Converter={converters:RatioConverter}, ConverterParameter='0.60'}"
Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.60'}">
<adonisControls:AdonisWindow.Style>
<Style TargetType="adonisControls:AdonisWindow" BasedOn="{StaticResource {x:Type adonisControls:AdonisWindow}}" >
<Setter Property="Title" Value="Model Viewer" />
</Style>
</adonisControls:AdonisWindow.Style>
<adonisControls:AdonisWindow.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Resources.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</adonisControls:AdonisWindow.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="350" MinWidth="250" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="4*" />
</Grid.ColumnDefinitions>
<GroupBox Grid.Column="0" Padding="{adonisUi:Space 0}" Background="Transparent">
<DockPanel Margin="10">
<Grid DockPanel.Dock="Top">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Models" VerticalAlignment="Center" Margin="0 0 0 10" />
<ComboBox Grid.Row="0" Grid.Column="2" Style="{StaticResource ModelsComboBox}" IsEnabled="{Binding IsReady}" />
<TextBlock Grid.Row="1" Grid.Column="0" Text="Triangles" VerticalAlignment="Center" Margin="0 0 0 10" />
<TextBlock Grid.Row="1" Grid.Column="2" Text="{Binding ModelViewer.SelectedModel.TriangleCount, FallbackValue=0, StringFormat={}{0:### ### ### ###}}" VerticalAlignment="Center" HorizontalAlignment="Right" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Materials" VerticalAlignment="Center" Margin="0 0 0 10" />
<TextBlock Grid.Row="2" Grid.Column="2" Text="{Binding ModelViewer.SelectedModel.Group3d.Count, FallbackValue=0, StringFormat={}{0:### ###}}" VerticalAlignment="Center" HorizontalAlignment="Right" />
<Grid Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="3">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="10" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0" Content="Focus" Click="OnFocusClick" IsEnabled="{Binding IsReady}" />
<ToggleButton Grid.Row="0" Grid.Column="2" IsChecked="{Binding ModelViewer.AppendMode}" IsEnabled="{Binding IsReady}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarToggleButton}}">
<TextBlock Text="{Binding IsChecked, Converter={x:Static converters:BoolToToggleConverter.Instance},
StringFormat={}Append {0}, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ToggleButton}}}" />
</ToggleButton>
<Button Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="3" Click="Save" IsEnabled="{Binding IsReady}"
Style="{DynamicResource {x:Static adonisUi:Styles.AccentButton}}">
<TextBlock Text="{Binding ModelViewer.LoadedModelsView.Count, StringFormat={}Save All Loaded Models ({0})}" />
</Button>
</Grid>
</Grid>
<Separator DockPanel.Dock="Top" Tag="MATERIALS" Style="{StaticResource CustomSeparator}" />
<ListBox x:Name="MaterialsListName" DockPanel.Dock="Top" Style="{StaticResource MaterialsListBox}" IsEnabled="{Binding IsReady}">
<ListBox.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Copy Name" Click="OnCopyClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource CopyIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Overwrite Material" Click="OnOverwriteMaterialClick">
<MenuItem.Icon>
<Viewbox Width="16" Height="16">
<Canvas Width="24" Height="24">
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource OverwriteIcon}" />
</Canvas>
</Viewbox>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
</DockPanel>
</GroupBox>
<GridSplitter Grid.Column="1" ResizeDirection="Columns" Width="4" VerticalAlignment="Stretch" ResizeBehavior="PreviousAndNext"
Background="{DynamicResource {x:Static adonisUi:Brushes.Layer3BackgroundBrush}}" />
<TextBlock Grid.Column="2" Text="Model is loading, please wait..." HorizontalAlignment="Center" VerticalAlignment="Center"/>
<helix:Viewport3DX Grid.Column="2" EffectsManager="{Binding ModelViewer.EffectManager}" Camera="{Binding ModelViewer.Cam}"
ShowViewCube="False" IsChangeFieldOfViewEnabled="False" IsMoveEnabled="False" UseDefaultGestures="False"
ShowCameraTarget="False" FXAALevel="Ultra" MSAA="Maximum" BackgroundColor="#2A2B34"
EnableSSAO="True" SSAOIntensity="1" EnableSwapChainRendering="True">
<helix:Viewport3DX.InputBindings>
<MouseBinding Command="helix:ViewportCommands.Rotate" Gesture="LeftClick" />
<MouseBinding Command="helix:ViewportCommands.Zoom" Gesture="RightClick" />
<MouseBinding Command="helix:ViewportCommands.Pan" Gesture="MiddleClick" />
<KeyBinding Command="helix:ViewportCommands.ZoomExtents" Modifiers="Shift" Key="C"/>
</helix:Viewport3DX.InputBindings>
<helix:EnvironmentMap3D Texture="{Binding ModelViewer.HDRi}" />
<helix:DirectionalLight3D Color="White" Direction="{Binding Camera.LookDirection,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type helix:Viewport3DX}}}" />
<helix:LineGeometryModel3D Geometry="{Binding ModelViewer.SelectedModel.XAxis}" Color="#FC3854" />
<helix:LineGeometryModel3D Geometry="{Binding ModelViewer.SelectedModel.YAxis}" Color="#85CB22" />
<helix:LineGeometryModel3D Geometry="{Binding ModelViewer.SelectedModel.ZAxis}" Color="#388EED" />
<helix:GroupModel3D x:Name="MyAntiCrashGroup" Mouse3DDown="OnMouse3DDown"
ItemsSource="{Binding ModelViewer.SelectedModel.Group3d, IsAsync=True}"/>
</helix:Viewport3DX>
</Grid>
</adonisControls:AdonisWindow>

View File

@@ -1,115 +0,0 @@
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;
using AdonisUI.Controls;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Material;
using FModel.Services;
using FModel.ViewModels;
using HelixToolkit.Wpf.SharpDX;
using MessageBox = AdonisUI.Controls.MessageBox;
using MessageBoxImage = AdonisUI.Controls.MessageBoxImage;
namespace FModel.Views;
public partial class ModelViewer
{
private bool _messageShown;
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
public ModelViewer()
{
DataContext = _applicationView;
InitializeComponent();
}
public void Load(UObject export) => _applicationView.ModelViewer.LoadExport(export);
public void Overwrite(UMaterialInstance materialInstance)
{
if (_applicationView.ModelViewer.TryOverwriteMaterial(materialInstance))
{
_applicationView.CUE4Parse.ModelIsOverwritingMaterial = false;
}
else
{
MessageBox.Show(new MessageBoxModel
{
Text = "An attempt to load a material failed.",
Caption = "Error",
Icon = MessageBoxImage.Error,
Buttons = MessageBoxButtons.OkCancel(),
IsSoundEnabled = false
});
}
}
private void OnClosing(object sender, CancelEventArgs e)
{
_applicationView.ModelViewer.Clear();
_applicationView.ModelViewer.AppendMode = false;
_applicationView.CUE4Parse.ModelIsOverwritingMaterial = false;
MyAntiCrashGroup.ItemsSource = null; // <3
}
private async void OnWindowKeyDown(object sender, KeyEventArgs e)
{
switch (e.Key)
{
case Key.W:
_applicationView.ModelViewer.WirefreameToggle();
break;
case Key.H:
_applicationView.ModelViewer.RenderingToggle();
break;
case Key.D:
_applicationView.ModelViewer.DiffuseOnlyToggle();
break;
case Key.M:
_applicationView.ModelViewer.MaterialColorToggle();
break;
case Key.Decimal:
_applicationView.ModelViewer.FocusOnSelectedMesh();
break;
case Key.S when Keyboard.Modifiers.HasFlag(ModifierKeys.Control) && Keyboard.Modifiers.HasFlag(ModifierKeys.Shift):
_applicationView.ModelViewer.SaveAsScene();
break;
case Key.S when Keyboard.Modifiers.HasFlag(ModifierKeys.Control):
await _applicationView.ModelViewer.SaveLoadedModels();
break;
}
}
private void OnMouse3DDown(object sender, MouseDown3DEventArgs e)
{
if (!Keyboard.Modifiers.HasFlag(ModifierKeys.Shift) || e.HitTestResult.ModelHit is not CustomMeshGeometryModel3D m) return;
_applicationView.ModelViewer.SelectedModel.SelectedGeometry = m;
MaterialsListName.ScrollIntoView(m);
}
private void OnFocusClick(object sender, RoutedEventArgs e)
=> _applicationView.ModelViewer.FocusOnSelectedMesh();
private void OnCopyClick(object sender, RoutedEventArgs e)
=> _applicationView.ModelViewer.CopySelectedMaterialName();
private async void Save(object sender, RoutedEventArgs e)
=> await _applicationView.ModelViewer.SaveLoadedModels();
private void OnOverwriteMaterialClick(object sender, RoutedEventArgs e)
{
_applicationView.CUE4Parse.ModelIsOverwritingMaterial = true;
if (!_messageShown)
{
MessageBox.Show(new MessageBoxModel
{
Text = "Simply extract a material once FModel will be brought to the foreground. This message will be shown once per Model Viewer's lifetime, close it to begin.",
Caption = "How To Overwrite Material?",
Icon = MessageBoxImage.Information,
IsSoundEnabled = false
});
_messageShown = true;
}
MainWindow.YesWeCats.Activate();
}
}

View File

@@ -75,8 +75,8 @@ public class GamePathVisualLineText : VisualLineText
}
else
{
await _threadWorkerView.Begin(_ =>
_applicationView.CUE4Parse.ExtractAndScroll(fullPath, obj));
await _threadWorkerView.Begin(cancellationToken =>
_applicationView.CUE4Parse.ExtractAndScroll(cancellationToken, fullPath, obj));
}
};
return a;

View File

@@ -1,29 +0,0 @@
using System;
using System.Globalization;
using System.Windows.Data;
using SharpDX.Direct3D11;
namespace FModel.Views.Resources.Converters;
public class BoolToFillModeConverter : IValueConverter
{
public static readonly BoolToFillModeConverter Instance = new();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value switch
{
FillMode.Solid => true,
_ => false
};
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value switch
{
true => FillMode.Solid,
_ => FillMode.Wireframe
};
}
}

View File

@@ -1,20 +0,0 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace FModel.Views.Resources.Converters;
public class DateTimeToStringConverter : IValueConverter
{
public static readonly DateTimeToStringConverter Instance = new();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value is not DateTime dateTime ? value : $"{dateTime.ToLongDateString()}, {dateTime.ToShortTimeString()}";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

View File

@@ -31,6 +31,7 @@ public class StringToGameConverter : IValueConverter
578080 => FGame.TslGame,
677620 => FGame.PortalWars,
1172620 => FGame.Athena,
1665460 => FGame.eFootball,
_ => FGame.Unknown
};
return ret == FGame.Unknown ? value : ret.GetDescription();

View File

@@ -1,27 +0,0 @@
using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Media;
using HelixToolkit.Wpf.SharpDX;
namespace FModel.Views.Resources.Converters;
public class TagToColorConverter : IValueConverter
{
public static readonly TagToColorConverter Instance = new();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is not PBRMaterial material)
return new SolidColorBrush(Colors.Red);
return new SolidColorBrush(Color.FromScRgb(
material.AlbedoColor.Alpha, material.AlbedoColor.Red,
material.AlbedoColor.Green, material.AlbedoColor.Blue));
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

View File

@@ -4,7 +4,6 @@
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:controls="clr-namespace:FModel.Views.Resources.Controls"
xmlns:soundOut="clr-namespace:CSCore.SoundOut;assembly=CSCore"
xmlns:sharpDx="clr-namespace:SharpDX.Direct3D11;assembly=SharpDX.Direct3D11"
xmlns:audioControls="clr-namespace:FModel.Views.Resources.Controls.Aup"
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
xmlns:avalonedit="http://icsharpcode.net/sharpdevelop/avalonedit"
@@ -65,7 +64,7 @@
<Geometry x:Key="FoldIcon">M8.12 19.3c.39.39 1.02.39 1.41 0L12 16.83l2.47 2.47c.39.39 1.02.39 1.41 0 .39-.39.39-1.02 0-1.41l-3.17-3.17c-.39-.39-1.02-.39-1.41 0l-3.17 3.17c-.4.38-.4 1.02-.01 1.41zm7.76-14.6c-.39-.39-1.02-.39-1.41 0L12 7.17 9.53 4.7c-.39-.39-1.02-.39-1.41 0-.39.39-.39 1.03 0 1.42l3.17 3.17c.39.39 1.02.39 1.41 0l3.17-3.17c.4-.39.4-1.03.01-1.42z</Geometry>
<Geometry x:Key="UnfoldIcon">M12 5.83l2.46 2.46c.39.39 1.02.39 1.41 0 .39-.39.39-1.02 0-1.41L12.7 3.7c-.39-.39-1.02-.39-1.41 0L8.12 6.88c-.39.39-.39 1.02 0 1.41.39.39 1.02.39 1.41 0L12 5.83zm0 12.34l-2.46-2.46c-.39-.39-1.02-.39-1.41 0-.39.39-.39 1.02 0 1.41l3.17 3.18c.39.39 1.02.39 1.41 0l3.17-3.17c.39-.39.39-1.02 0-1.41-.39-.39-1.02-.39-1.41 0L12 18.17z</Geometry>
<Geometry x:Key="LocateMeIcon">M11.71,17.99C8.53,17.84,6,15.22,6,12c0-3.31,2.69-6,6-6c3.22,0,5.84,2.53,5.99,5.71l-2.1-0.63C15.48,9.31,13.89,8,12,8 c-2.21,0-4,1.79-4,4c0,1.89,1.31,3.48,3.08,3.89L11.71,17.99z M22,12c0,0.3-0.01,0.6-0.04,0.9l-1.97-0.59C20,12.21,20,12.1,20,12 c0-4.42-3.58-8-8-8s-8,3.58-8,8s3.58,8,8,8c0.1,0,0.21,0,0.31-0.01l0.59,1.97C12.6,21.99,12.3,22,12,22C6.48,22,2,17.52,2,12 C2,6.48,6.48,2,12,2S22,6.48,22,12z M18.23,16.26l2.27-0.76c0.46-0.15,0.45-0.81-0.01-0.95l-7.6-2.28 c-0.38-0.11-0.74,0.24-0.62,0.62l2.28,7.6c0.14,0.47,0.8,0.48,0.95,0.01l0.76-2.27l3.91,3.91c0.2,0.2,0.51,0.2,0.71,0l1.27-1.27 c0.2-0.2,0.2-0.51,0-0.71L18.23,16.26z</Geometry>
<Geometry x:Key="MeshIcon">M12 2c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2zm8 7h-5v12c0 .55-.45 1-1 1s-1-.45-1-1v-5h-2v5c0 .55-.45 1-1 1s-1-.45-1-1V9H4c-.55 0-1-.45-1-1s.45-1 1-1h16c.55 0 1 .45 1 1s-.45 1-1 1z</Geometry>
<Geometry x:Key="MeshIcon">M1.8 6q-.525 0-.887-.35Q.55 5.3.55 4.8V4q0-1.425 1.012-2.438Q2.575.55 4 .55h.8q.5 0 .85.362.35.363.35.888 0 .5-.35.85T4.8 3H4q-.425 0-.712.287Q3 3.575 3 4v.8q0 .5-.35.85T1.8 6ZM4 23.45q-1.425 0-2.438-1.012Q.55 21.425.55 20v-.8q0-.5.363-.85.362-.35.887-.35.5 0 .85.35t.35.85v.8q0 .425.288.712Q3.575 21 4 21h.8q.5 0 .85.35t.35.85q0 .525-.35.887-.35.363-.85.363Zm15.2 0q-.5 0-.85-.363-.35-.362-.35-.887 0-.5.35-.85t.85-.35h.8q.425 0 .712-.288Q21 20.425 21 20v-.8q0-.5.35-.85t.85-.35q.525 0 .888.35.362.35.362.85v.8q0 1.425-1.012 2.438Q21.425 23.45 20 23.45ZM22.2 6q-.5 0-.85-.35T21 4.8V4q0-.425-.288-.713Q20.425 3 20 3h-.8q-.5 0-.85-.35T18 1.8q0-.525.35-.888.35-.362.85-.362h.8q1.425 0 2.438 1.012Q23.45 2.575 23.45 4v.8q0 .5-.362.85-.363.35-.888.35ZM12 17.35l1-.575v-4.1l3.55-2.075V9.425l-1-.575L12 10.925 8.45 8.85l-1 .575V10.6L11 12.675v4.1Zm-1.325 2.325-4.55-2.65q-.625-.35-.975-.963-.35-.612-.35-1.337V9.45q0-.725.35-1.337.35-.613.975-.963l4.55-2.65Q11.3 4.15 12 4.15t1.325.35l4.55 2.65q.625.35.975.963.35.612.35 1.337v5.275q0 .725-.35 1.337-.35.613-.975.963l-4.55 2.65q-.625.35-1.325.35t-1.325-.35Z</Geometry>
<Geometry x:Key="MaterialIcon">M11 9h2v2h-2V9zm-2 2h2v2H9v-2zm4 0h2v2h-2v-2zm2-2h2v2h-2V9zM7 9h2v2H7V9zm12-6H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9 18H7v-2h2v2zm4 0h-2v-2h2v2zm4 0h-2v-2h2v2zm2-7h-2v2h2v2h-2v-2h-2v2h-2v-2h-2v2H9v-2H7v2H5v-2h2v-2H5V6c0-.55.45-1 1-1h12c.55 0 1 .45 1 1v5z</Geometry>
<Geometry x:Key="VisibleIcon">M12 4C7 4 2.73 7.11 1 11.5 2.73 15.89 7 19 12 19s9.27-3.11 11-7.5C21.27 7.11 17 4 12 4zm0 12.5c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z</Geometry>
<Geometry x:Key="NotVisibleIcon">M12 6.5c2.76 0 5 2.24 5 5 0 .51-.1 1-.24 1.46l3.06 3.06c1.39-1.23 2.49-2.77 3.18-4.53C21.27 7.11 17 4 12 4c-1.27 0-2.49.2-3.64.57l2.17 2.17c.47-.14.96-.24 1.47-.24zM2.71 3.16c-.39.39-.39 1.02 0 1.41l1.97 1.97C3.06 7.83 1.77 9.53 1 11.5 2.73 15.89 7 19 12 19c1.52 0 2.97-.3 4.31-.82l2.72 2.72c.39.39 1.02.39 1.41 0 .39-.39.39-1.02 0-1.41L4.13 3.16c-.39-.39-1.03-.39-1.42 0zM12 16.5c-2.76 0-5-2.24-5-5 0-.77.18-1.5.49-2.14l1.57 1.57c-.03.18-.06.37-.06.57 0 1.66 1.34 3 3 3 .2 0 .38-.03.57-.07L14.14 16c-.65.32-1.37.5-2.14.5zm2.97-5.33c-.15-1.4-1.25-2.49-2.64-2.64l2.64 2.64z</Geometry>
@@ -638,91 +637,6 @@
</Setter>
</Style>
<Style x:Key="MaterialsListBox" TargetType="ListBox" BasedOn="{StaticResource {x:Type ListBox}}">
<Setter Property="ItemsSource" Value="{Binding ModelViewer.SelectedModel.Group3d, IsAsync=True}" />
<Setter Property="SelectedItem" Value="{Binding ModelViewer.SelectedModel.SelectedGeometry}" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled" />
<Setter Property="adonisExtensions:ScrollViewerExtension.VerticalScrollBarExpansionMode" Value="NeverExpand"/>
<Setter Property="adonisExtensions:ScrollViewerExtension.VerticalScrollBarPlacement" Value="Docked"/>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="25" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Source="/FModel;component/Resources/materialicon.png" Width="16" Height="16" Margin="5 0" HorizontalAlignment="Center" />
<TextBlock Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Center" Text="{Binding DisplayName}" TextTrimming="CharacterEllipsis" />
<ToggleButton Grid.Column="3" IsChecked="{Binding IsRendering}" Padding="3" Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarToggleButton}}">
<Viewbox Width="16" Height="16" HorizontalAlignment="Center">
<Canvas Width="24" Height="24">
<Path x:Name="SvgIcon1" Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource VisibleIcon}" />
</Canvas>
</Viewbox>
</ToggleButton>
<ToggleButton Grid.Column="4" IsChecked="{Binding FillMode, Converter={x:Static converters:BoolToFillModeConverter.Instance}}" Padding="3" Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarToggleButton}}">
<Viewbox Width="16" Height="16" HorizontalAlignment="Center">
<Canvas Width="24" Height="24">
<Path x:Name="SvgIcon2" Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource WireframeIcon}" />
</Canvas>
</Viewbox>
</ToggleButton>
<Rectangle Grid.Column="5" Width="19" Height="22" Fill="{Binding Material, Converter={x:Static converters:TagToColorConverter.Instance}}"
Visibility="{Binding DataContext.ModelViewer.SelectedModel.ShowMaterialColor,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:Views.ModelViewer}},
Converter={StaticResource BoolToVisibilityConverter}}" />
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsRendering}" Value="True">
<Setter TargetName="SvgIcon1" Property="Data" Value="{StaticResource VisibleIcon}" />
<Setter TargetName="SvgIcon1" Property="Fill" Value="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" />
</DataTrigger>
<DataTrigger Binding="{Binding IsRendering}" Value="False">
<Setter TargetName="SvgIcon1" Property="Data" Value="{StaticResource NotVisibleIcon}" />
<Setter TargetName="SvgIcon1" Property="Fill" Value="{DynamicResource {x:Static adonisUi:Brushes.ErrorBrush}}" />
</DataTrigger>
<DataTrigger Binding="{Binding FillMode}" Value="{x:Static sharpDx:FillMode.Solid}">
<Setter TargetName="SvgIcon2" Property="Data" Value="{StaticResource WireframeIcon}" />
<Setter TargetName="SvgIcon2" Property="Fill" Value="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" />
</DataTrigger>
<DataTrigger Binding="{Binding FillMode}" Value="{x:Static sharpDx:FillMode.Wireframe}">
<Setter TargetName="SvgIcon2" Property="Data" Value="{StaticResource NotWireframeIcon}" />
<Setter TargetName="SvgIcon2" Property="Fill" Value="{DynamicResource {x:Static adonisUi:Brushes.ErrorBrush}}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource {x:Type ListBoxItem}}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Padding" Value="0" />
</Style>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding ModelViewer.SelectedModel.Group3d.Count, FallbackValue=0}" Value="0">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<TextBlock Text="No material found in the mesh" FontWeight="SemiBold" TextAlignment="Center"
Foreground="{DynamicResource {x:Static adonisUi:Brushes.ErrorBrush}}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="GameFilesTabControl" TargetType="TabControl" BasedOn="{StaticResource {x:Type TabControl}}">
<Setter Property="ItemsSource" Value="{Binding CUE4Parse.TabControl.TabsItems, IsAsync=True}" />
<Setter Property="SelectedItem" Value="{Binding CUE4Parse.TabControl.SelectedTab}" />

View File

@@ -100,10 +100,10 @@
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="225" Header="Package" adonisExtensions:GridViewSortExtension.PropertyName="Package">
<GridViewColumn Width="225" Header="Archive" adonisExtensions:GridViewSortExtension.PropertyName="Archive">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock HorizontalAlignment="Left" Text="{Binding Package}" />
<TextBlock HorizontalAlignment="Left" Text="{Binding Archive}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>

View File

@@ -58,7 +58,7 @@ public partial class SearchView
return;
WindowState = WindowState.Minimized;
await _threadWorkerView.Begin(_ => _applicationView.CUE4Parse.Extract(assetItem.FullPath, true));
await _threadWorkerView.Begin(cancellationToken => _applicationView.CUE4Parse.Extract(cancellationToken, assetItem.FullPath, true));
MainWindow.YesWeCats.Activate();
}
@@ -80,4 +80,4 @@ public partial class SearchView
return;
}
}
}
}

View File

@@ -307,6 +307,7 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
@@ -371,25 +372,30 @@
IsChecked="{Binding SaveMaterials, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"/>
</UniformGrid>
<TextBlock Grid.Row="6" Grid.Column="0" Text="Auto-Export Morph Targets" VerticalAlignment="Center" Margin="0 0 0 5" />
<TextBlock Grid.Row="6" Grid.Column="0" Text="Preview Levels (.umap)" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="6" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding PreviewWorlds, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/>
<TextBlock Grid.Row="7" Grid.Column="0" Text="Auto-Export Morph Targets" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="7" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding SaveMorphTargets, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/>
<TextBlock Grid.Row="7" Grid.Column="0" Text="Auto-Export Animations" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="7" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
<TextBlock Grid.Row="8" Grid.Column="0" Text="Auto-Export Animations" VerticalAlignment="Center" Margin="0 0 0 5" />
<CheckBox Grid.Row="8" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding SaveAnimations, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/>
<TextBlock Grid.Row="8" Grid.Column="0" Text="Export Skeletons as Empty Meshes" VerticalAlignment="Center" />
<CheckBox Grid.Row="8" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
<TextBlock Grid.Row="9" Grid.Column="0" Text="Export Skeletons as Empty Meshes" VerticalAlignment="Center" />
<CheckBox Grid.Row="9" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
IsChecked="{Binding SaveSkeletonAsMesh, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 0"/>
<Separator Grid.Row="9" Grid.Column="0" Grid.ColumnSpan="5" Style="{StaticResource CustomSeparator}" />
<Separator Grid.Row="10" Grid.Column="0" Grid.ColumnSpan="5" Style="{StaticResource CustomSeparator}" />
<TextBlock Grid.Row="10" Grid.Column="0" Text="Texture Format" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="10" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.TextureExportFormats}" SelectedItem="{Binding SettingsView.SelectedTextureExportFormat, Mode=TwoWay}"
<TextBlock Grid.Row="11" Grid.Column="0" Text="Texture Format" VerticalAlignment="Center" Margin="0 0 0 5" />
<ComboBox Grid.Row="11" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.TextureExportFormats}" SelectedItem="{Binding SettingsView.SelectedTextureExportFormat, Mode=TwoWay}"
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
<ComboBox.ItemTemplate>
<DataTemplate>

View File

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

View File

@@ -0,0 +1,48 @@
using System;
using OpenTK.Graphics.OpenGL4;
namespace FModel.Views.Snooper;
public class BufferObject<TDataType> : IDisposable where TDataType : unmanaged
{
private readonly int _handle;
private readonly BufferTarget _bufferTarget;
private BufferObject(BufferTarget bufferTarget)
{
_bufferTarget = bufferTarget;
_handle = GL.GenBuffer();
Bind();
}
public unsafe BufferObject(TDataType[] data, BufferTarget bufferTarget) : this(bufferTarget)
{
GL.BufferData(bufferTarget, data.Length * sizeof(TDataType), data, BufferUsageHint.StaticDraw);
}
public unsafe void Update(int offset, TDataType data)
{
GL.BufferSubData(_bufferTarget, (IntPtr) (offset * sizeof(TDataType)), sizeof(TDataType), ref data);
}
public unsafe void Update(TDataType[] data)
{
GL.BufferSubData(_bufferTarget, IntPtr.Zero, data.Length * sizeof(TDataType), data);
}
public void Bind()
{
GL.BindBuffer(_bufferTarget, _handle);
}
public void Unbind()
{
GL.BindBuffer(_bufferTarget, 0);
}
public void Dispose()
{
GL.DeleteBuffer(_handle);
}
}

View File

@@ -0,0 +1,101 @@
using System;
using System.Numerics;
using ImGuiNET;
namespace FModel.Views.Snooper;
public class Camera
{
public Vector3 Position;
public Vector3 Direction;
public Vector3 Up = Vector3.UnitY;
public float Yaw = -90f;
public float Pitch = 0f;
public float Zoom = 60f;
public float Speed = 1f;
public float Near = 0.01f;
public float Far = 100f;
public float AspectRatio = 16f / 9f;
public Camera()
{
Position = new Vector3(0, 1, 1);
Direction = Vector3.Zero;
InitDirection();
}
public Camera(Vector3 position, Vector3 direction, float near, float far, float speed)
{
Position = position;
Direction = direction;
Near = near;
Far = far;
Speed = speed;
InitDirection();
}
private void InitDirection()
{
// trigonometric math to calculate the cam's yaw/pitch based on position and direction to look
var yaw = MathF.Atan((-Position.X - Direction.X) / (Position.Z - Direction.Z));
var pitch = MathF.Atan((Position.Y - Direction.Y) / (Position.Z - Direction.Z));
ModifyDirection(Helper.RadiansToDegrees(yaw), Helper.RadiansToDegrees(pitch));
}
public 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 void ModifyDirection(float xOffset, float yOffset)
{
Yaw += xOffset;
Pitch -= yOffset;
//We don't want to be able to look behind us by going over our head or under our feet so make sure it stays within these bounds
Pitch = Math.Clamp(Pitch, -89f, 89f);
var direction = Vector3.Zero;
var yaw = Helper.DegreesToRadians(Yaw);
var pitch = Helper.DegreesToRadians(Pitch);
direction.X = MathF.Cos(yaw) * MathF.Cos(pitch);
direction.Y = MathF.Sin(pitch);
direction.Z = MathF.Sin(yaw) * MathF.Cos(pitch);
Direction = Vector3.Normalize(direction);
}
public Matrix4x4 GetViewMatrix()
{
return Matrix4x4.CreateLookAt(Position, Position + Direction, Up);
}
public Matrix4x4 GetProjectionMatrix()
{
return 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 System.Numerics.Vector2(8, 3));
ImGui.PushStyleVar(ImGuiStyleVar.CellPadding, new System.Numerics.Vector2(0, 1));
if (ImGui.BeginTable("camera_editor", 2))
{
SnimGui.Layout("Speed");ImGui.PushID(1);
ImGui.DragFloat("", ref Speed, _step, _zero, _infinite, "%.2f m/s", _clamp);
ImGui.PopID();SnimGui.Layout("Far Plane");ImGui.PushID(2);
ImGui.DragFloat("", ref Far, 0.1f, 0.1f, Far * 2f, "%.2f m", _clamp);
ImGui.PopID();
ImGui.EndTable();
}
ImGui.PopStyleVar(2);
}
}

View File

@@ -0,0 +1,72 @@
using CUE4Parse.UE4.Assets.Exports.Material;
namespace FModel.Views.Snooper;
public class Cube : Model
{
public Cube(UMaterialInterface unrealMaterial) : base(unrealMaterial)
{
Indices = new uint[]
{
0, 1, 2, 3, 4, 5,
6, 7, 8, 9, 10, 11,
12, 13, 14, 15, 16,
17, 18, 19, 20, 21,
22, 23, 24, 25, 26,
27, 28, 29, 30, 31,
32, 33, 34, 35
};
Vertices = new float[] {
// I X Y Z Normals Tangent U V Layer
-1, -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, .5f,
-1, 0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, .5f,
-1, 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, .5f,
-1, 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, .5f,
-1, -0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, .5f,
-1, -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, .5f,
-1, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, .5f,
-1, 0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, .5f,
-1, 0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, .5f,
-1, 0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, .5f,
-1, -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, .5f,
-1, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, .5f,
-1, -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, .5f,
-1, -0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, .5f,
-1, -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, .5f,
-1, -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, .5f,
-1, -0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, .5f,
-1, -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, .5f,
-1, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, .5f,
-1, 0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, .5f,
-1, 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, .5f,
-1, 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, .5f,
-1, 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, .5f,
-1, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, .5f,
-1, -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, .5f,
-1, 0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, .5f,
-1, 0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, .5f,
-1, 0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, .5f,
-1, -0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, .5f,
-1, -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, .5f,
-1, -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, .5f,
-1, 0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, .5f,
-1, 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, .5f,
-1, 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, .5f,
-1, -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, .5f,
-1, -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, .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);
}
}

View File

@@ -0,0 +1,125 @@
using System;
using OpenTK.Graphics.OpenGL4;
namespace FModel.Views.Snooper;
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);
}
}

View File

@@ -0,0 +1,66 @@
using System;
using System.Numerics;
using OpenTK.Graphics.OpenGL4;
namespace FModel.Views.Snooper;
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.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);
}
public void Dispose()
{
_ebo?.Dispose();
_vbo?.Dispose();
_vao?.Dispose();
_shader?.Dispose();
GL.DeleteProgram(_handle);
}
}

View File

@@ -0,0 +1,107 @@
using System;
using System.Numerics;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.Core.Misc;
using ImGuiNET;
using OpenTK.Graphics.OpenGL4;
namespace FModel.Views.Snooper;
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 readonly Transform Transform;
public Vector4 Color;
public float Intensity;
public bool IsSetup;
public Light(FGuid model, Texture icon, UObject parent, UObject light, FVector position)
{
var p = light.GetOrDefault("RelativeLocation", parent.GetOrDefault("RelativeLocation", FVector.ZeroVector));
var r = light.GetOrDefault("RelativeRotation", parent.GetOrDefault("RelativeRotation", FRotator.ZeroRotator));
Transform = Transform.Identity;
Transform.Scale = new FVector(0.25f);
Transform.Position = position + r.RotateVector(p.ToMapVector()) * Constants.SCALE_DOWN_RATIO;
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 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)
{
_vao.Bind();
Icon?.Bind(TextureUnit.Texture0);
shader.SetUniform("uIcon", 0);
shader.SetUniform("uColor", Color);
GL.DrawArrays(PrimitiveType.Triangles, 0, Indices.Length);
}
public virtual void Render(int i, Shader shader)
{
shader.SetUniform($"uLights[{i}].Color", Color);
shader.SetUniform($"uLights[{i}].Position", Transform.Position);
shader.SetUniform($"uLights[{i}].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);
}
}

View File

@@ -0,0 +1,358 @@
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 ImGuiNET;
using OpenTK.Graphics.OpenGL4;
namespace FModel.Views.Snooper;
public class Material : IDisposable
{
private int _handle;
public readonly CMaterialParams2 Parameters;
public string Name;
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 AoParams Ao;
public bool HasAo;
public float Roughness = 0.5f;
public float EmissiveMult = 1f;
public float UVScale = 1f;
public Material()
{
Parameters = new CMaterialParams2();
Name = "";
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>();
}
public Material(UMaterialInterface unrealMaterial) : this()
{
SwapMaterial(unrealMaterial);
}
public void SwapMaterial(UMaterialInterface unrealMaterial)
{
Name = unrealMaterial.Name;
unrealMaterial.GetParams(Parameters);
}
public void Setup(Options options, int numTexCoords)
{
_handle = GL.CreateProgram();
if (numTexCoords < 1 || Parameters.IsNull)
{
Diffuse = new[] { new Texture(new FLinearColor(1f, 0f, 0f, 1f)) };
Normals = new[] { new Texture(new FLinearColor(0.498f, 0.498f, 0.996f, 1f))};
SpecularMasks = new Texture[1];
Emissive = new Texture[1];
DiffuseColor = new[] { new Vector4(0.5f) };
EmissiveColor = new[] { Vector4.One };
}
else
{
{ // textures
Diffuse = FillTextures(options, numTexCoords, Parameters.HasTopDiffuse, CMaterialParams2.Diffuse, CMaterialParams2.FallbackDiffuse, true);
Normals = FillTextures(options, numTexCoords, Parameters.HasTopNormals, CMaterialParams2.Normals, CMaterialParams2.FallbackNormals);
SpecularMasks = FillTextures(options, numTexCoords, Parameters.HasTopSpecularMasks, CMaterialParams2.SpecularMasks, CMaterialParams2.FallbackSpecularMasks);
Emissive = FillTextures(options, numTexCoords, true, CMaterialParams2.Emissive, CMaterialParams2.FallbackEmissive);
}
{ // colors
DiffuseColor = FillColors(numTexCoords, Diffuse, CMaterialParams2.DiffuseColors, Vector4.One);
EmissiveColor = FillColors(numTexCoords, Emissive, CMaterialParams2.EmissiveColors, Vector4.One);
}
{ // scalars
if (Parameters.TryGetTexture2d(out var original, "M", "PackedTexture", "AEM") &&
!original.Name.Equals("T_BlackMask") && options.TryGetTexture(original, false, out var transformed))
{
HasAo = true;
Ao = new AoParams { Texture = transformed, AmbientOcclusion = 0.7f };
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") &&
Parameters.TryGetScalar(out var roughnessMax, "RoughnessMax", "SpecRoughnessMax"))
Roughness = (roughnessMin + roughnessMax) / 2f;
if (Parameters.TryGetScalar(out var roughness, "Rough", "Roughness"))
Roughness = roughness;
if (Parameters.TryGetScalar(out var emissiveMult, "emissive mult", "Emissive_Mult"))
EmissiveMult = emissiveMult;
if (Parameters.TryGetScalar(out var uvScale, "UV Scale"))
UVScale = uvScale;
}
}
}
/// <param name="options">just the cache object</param>
/// <param name="numTexCoords">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 numTexCoords, bool top, IReadOnlyList<string[]> triggers, string fallback, bool first = false)
{
UTexture2D original;
Texture transformed;
var fix = fallback == CMaterialParams2.FallbackSpecularMasks;
var textures = new Texture[numTexCoords];
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="numTexCoords">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 numTexCoords, IReadOnlyList<Texture> textures, IReadOnlyList<string[]> triggers, Vector4 fallback)
{
var colors = new Vector4[numTexCoords];
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.Ao.AmbientOcclusion", Ao.AmbientOcclusion);
shader.SetUniform("uParameters.HasAo", HasAo);
shader.SetUniform("uParameters.Roughness", Roughness);
shader.SetUniform("uParameters.EmissiveMult", EmissiveMult);
shader.SetUniform("uParameters.UVScale", UVScale);
}
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))
{
SnimGui.Layout("Roughness");ImGui.PushID(1);
ImGui.DragFloat("", ref Roughness, _step, _zero, 1.0f, _mult, _clamp);
SnimGui.Layout("Emissive Multiplier");ImGui.PushID(2);
ImGui.DragFloat("", ref EmissiveMult, _step, _zero, _infinite, _mult, _clamp);
ImGui.PopID();SnimGui.Layout("UV Scale");ImGui.PushID(3);
ImGui.DragFloat("", ref UVScale, _step, _zero, _infinite, _mult, _clamp);
ImGui.PopID();
if (HasAo)
{
SnimGui.Layout("Ambient Occlusion");ImGui.PushID(4);
ImGui.DragFloat("", ref Ao.AmbientOcclusion, _step, _zero, 1.0f, _mult, _clamp);ImGui.PopID();
if (Ao.HasColorBoost)
{
SnimGui.Layout("Color Boost");ImGui.PushID(5);
ImGui.ColorEdit3("", ref Ao.ColorBoost.Color);ImGui.PopID();
SnimGui.Layout("Color Boost Exponent");ImGui.PushID(6);
ImGui.DragFloat("", ref Ao.ColorBoost.Exponent, _step, _zero, _infinite, _mult, _clamp);
ImGui.PopID();
}
}
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())
{
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);
}
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 void ImGuiTextures(Dictionary<string, Texture> icons, Model model)
{
if (ImGui.BeginTable("material_textures", 2))
{
SnimGui.Layout("Channel");ImGui.PushID(1); ImGui.BeginDisabled(model.NumTexCoords < 2);
ImGui.DragInt("", ref SelectedChannel, _step, 0, model.NumTexCoords - 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:
SnimGui.Layout("Color");ImGui.PushID(3);
ImGui.ColorEdit4("", ref DiffuseColor[SelectedChannel], ImGuiColorEditFlags.NoAlpha);
ImGui.PopID();
break;
case 4:
SnimGui.Layout("Color");ImGui.PushID(3);
ImGui.ColorEdit4("", ref EmissiveColor[SelectedChannel], ImGuiColorEditFlags.NoAlpha);
ImGui.PopID();
break;
}
ImGui.EndTable();
}
ImGui.Image(GetSelectedTexture() ?? icons["noimage"].GetPointer(),
new Vector2(ImGui.GetContentRegionAvail().X - ImGui.GetScrollX()),
Vector2.Zero, Vector2.One, Vector4.One, new Vector4(1.0f, 1.0f, 1.0f, 0.25f));
ImGui.Spacing();
}
private IntPtr? GetSelectedTexture()
{
return SelectedTexture switch
{
0 => Diffuse[SelectedChannel]?.GetPointer(),
1 => Normals[SelectedChannel]?.GetPointer(),
2 => SpecularMasks[SelectedChannel]?.GetPointer(),
3 => Ao.Texture?.GetPointer(),
4 => Emissive[SelectedChannel]?.GetPointer(),
_ => null
};
}
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 float AmbientOcclusion;
public Boost ColorBoost;
public bool HasColorBoost;
}
public struct Boost
{
public Vector3 Color;
public float Exponent;
}

View File

@@ -0,0 +1,356 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Numerics;
using CUE4Parse_Conversion;
using CUE4Parse.UE4.Assets.Exports.Animation;
using CUE4Parse.UE4.Objects.UObject;
using CUE4Parse_Conversion.Meshes.PSK;
using CUE4Parse.UE4.Assets;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
using FModel.Extensions;
using FModel.Services;
using FModel.Settings;
using OpenTK.Graphics.OpenGL4;
namespace FModel.Views.Snooper;
public class Model : IDisposable
{
private int _handle;
private BufferObject<uint> _ebo;
private BufferObject<float> _vbo;
private BufferObject<float> _morphVbo;
private BufferObject<Matrix4x4> _matrixVbo;
private VertexArrayObject<float, uint> _vao;
private readonly UObject _export;
private readonly int _vertexSize = 13; // VertexIndex + Position + Normal + Tangent + UV + TextureLayer
private readonly uint[] _facesIndex = { 1, 0, 2 };
private const int _faceSize = 3; // just so we don't have to do .Length
public readonly string Path;
public readonly string Name;
public readonly string Type;
public readonly bool HasVertexColors;
public readonly bool HasMorphTargets;
public readonly int NumTexCoords;
public uint[] Indices;
public float[] Vertices;
public Section[] Sections;
public Material[] Materials;
public bool HasSkeleton => Skeleton is { IsLoaded: true };
public readonly Skeleton Skeleton;
public int TransformsCount;
public readonly List<Transform> Transforms;
public readonly Morph[] Morphs;
public bool Show;
public bool Wireframe;
public bool IsSetup;
public bool IsSelected;
public int SelectedInstance;
public float MorphTime;
protected Model(UObject export)
{
_export = export;
Path = _export.GetPathName();
Name = Path.SubstringAfterLast('/').SubstringBefore('.');
Type = export.ExportType;
NumTexCoords = 1;
Transforms = new List<Transform>();
}
public Model(UStaticMesh export, CStaticMesh staticMesh) : this(export, staticMesh, Transform.Identity) {}
public Model(UStaticMesh export, CStaticMesh staticMesh, Transform transform) : this(export, export.Materials, null, staticMesh.LODs[0], staticMesh.LODs[0].Verts, transform) {}
private Model(USkeletalMesh export, CSkeletalMesh skeletalMesh, Transform transform) : this(export, export.Materials, export.Skeleton, skeletalMesh.LODs[0], skeletalMesh.LODs[0].Verts, transform) {}
public Model(USkeletalMesh export, CSkeletalMesh skeletalMesh) : this(export, skeletalMesh, Transform.Identity)
{
var morphTargets = export.MorphTargets;
if (morphTargets is not { Length: > 0 })
return;
var length = morphTargets.Length;
HasMorphTargets = true;
Morphs = new Morph[length];
for (var i = 0; i < Morphs.Length; i++)
{
Morphs[i] = new Morph(Vertices, _vertexSize, morphTargets[i].Load<UMorphTarget>());
ApplicationService.ApplicationView.Status.UpdateStatusLabel($"{Morphs[i].Name} ... {i}/{length}");
}
ApplicationService.ApplicationView.Status.UpdateStatusLabel("");
}
private Model(UObject export, ResolvedObject[] materials, FPackageIndex skeleton, CBaseMeshLod lod, CMeshVertex[] vertices, Transform transform = null) : this(export)
{
NumTexCoords = lod.NumTexCoords;
Materials = new Material[materials.Length];
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();
}
if (lod.VertexColors is { Length: > 0})
{
HasVertexColors = true;
_vertexSize += 4; // + Color
}
if (skeleton != null)
{
Skeleton = new Skeleton(skeleton);
_vertexSize += 8; // + BoneIds + BoneWeights
}
var sections = lod.Sections.Value;
Sections = new Section[sections.Length];
Indices = new uint[sections.Sum(section => section.NumFaces * _faceSize)];
Vertices = new float[Indices.Length * _vertexSize];
for (var s = 0; s < sections.Length; s++)
{
var section = sections[s];
Sections[s] = new Section(section.MaterialIndex, section.NumFaces * _faceSize, section.FirstIndex, Materials[section.MaterialIndex]);
for (uint face = 0; face < section.NumFaces; face++)
{
foreach (var f in _facesIndex)
{
var count = 0;
var i = face * _faceSize + f;
var index = section.FirstIndex + i;
var baseIndex = index * _vertexSize;
var indice = lod.Indices.Value[index];
var vert = vertices[indice];
Vertices[baseIndex + count++] = indice;
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++] = lod.ExtraUV.IsValueCreated ? lod.ExtraUV.Value[0][indice].U : .5f;
if (HasVertexColors)
{
var color = lod.VertexColors[indice];
Vertices[baseIndex + count++] = color.R;
Vertices[baseIndex + count++] = color.G;
Vertices[baseIndex + count++] = color.B;
Vertices[baseIndex + count++] = color.A;
}
if (HasSkeleton)
{
var skelVert = (CSkelMeshVertex) vert;
var weightsHash = skelVert.UnpackWeights();
Vertices[baseIndex + count++] = skelVert.Bone[0];
Vertices[baseIndex + count++] = skelVert.Bone[1];
Vertices[baseIndex + count++] = skelVert.Bone[2];
Vertices[baseIndex + count++] = skelVert.Bone[3];
Vertices[baseIndex + count++] = weightsHash[0];
Vertices[baseIndex + count++] = weightsHash[1];
Vertices[baseIndex + count++] = weightsHash[2];
Vertices[baseIndex + count++] = weightsHash[3];
}
Indices[index] = i;
}
}
}
AddInstance(transform ?? Transform.Identity);
}
public void AddInstance(Transform transform)
{
TransformsCount++;
Transforms.Add(transform);
}
public void UpdateMatrix(int instance)
{
_matrixVbo.Bind();
_matrixVbo.Update(instance, Transforms[instance].Matrix);
_matrixVbo.Unbind();
}
public void UpdateMorph(int index)
{
_morphVbo.Bind();
_morphVbo.Update(Morphs[index].Vertices);
_morphVbo.Unbind();
}
public void SetupInstances()
{
var instanceMatrix = new Matrix4x4[TransformsCount];
for (var i = 0; i < instanceMatrix.Length; i++)
instanceMatrix[i] = Transforms[i].Matrix;
_matrixVbo = new BufferObject<Matrix4x4>(instanceMatrix, BufferTarget.ArrayBuffer);
_vao.BindInstancing(); // VertexAttributePointer
}
public void Setup(Options options)
{
_handle = GL.CreateProgram();
_ebo = new BufferObject<uint>(Indices, BufferTarget.ElementArrayBuffer);
_vbo = new BufferObject<float>(Vertices, BufferTarget.ArrayBuffer);
_vao = new VertexArrayObject<float, uint>(_vbo, _ebo);
_vao.VertexAttributePointer(0, 1, VertexAttribPointerType.Int, _vertexSize, 0); // vertex index
_vao.VertexAttributePointer(1, 3, VertexAttribPointerType.Float, _vertexSize, 1); // position
_vao.VertexAttributePointer(2, 3, VertexAttribPointerType.Float, _vertexSize, 4); // normal
_vao.VertexAttributePointer(3, 3, VertexAttribPointerType.Float, _vertexSize, 7); // tangent
_vao.VertexAttributePointer(4, 2, VertexAttribPointerType.Float, _vertexSize, 10); // uv
_vao.VertexAttributePointer(5, 1, VertexAttribPointerType.Float, _vertexSize, 12); // texture index
_vao.VertexAttributePointer(6, 4, VertexAttribPointerType.Float, _vertexSize, 13); // color
_vao.VertexAttributePointer(7, 4, VertexAttribPointerType.Float, _vertexSize, 17); // boneids
_vao.VertexAttributePointer(8, 4, VertexAttribPointerType.Float, _vertexSize, 21); // boneweights
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, NumTexCoords);
}
if (HasMorphTargets)
{
for (uint morph = 0; morph < Morphs.Length; morph++)
{
Morphs[morph].Setup();
if (morph == 0)
_morphVbo = new BufferObject<float>(Morphs[morph].Vertices, BufferTarget.ArrayBuffer);
}
_vao.Bind();
_vao.VertexAttributePointer(13, 3, VertexAttribPointerType.Float, 3, 0); // morph position
_vao.Unbind();
}
for (int section = 0; section < Sections.Length; section++)
{
if (!Show) Show = Sections[section].Show;
Sections[section].Setup();
}
IsSetup = true;
}
public void Render(Shader shader)
{
if (IsSelected)
{
GL.Enable(EnableCap.StencilTest);
GL.StencilFunc(StencilFunction.Always, 1, 0xFF);
}
_vao.Bind();
shader.SetUniform("uMorphTime", MorphTime);
shader.SetUniform("uNumTexCoords", NumTexCoords);
GL.PolygonMode(MaterialFace.FrontAndBack, Wireframe ? PolygonMode.Line : PolygonMode.Fill);
for (int section = 0; section < Sections.Length; section++)
{
Materials[Sections[section].MaterialIndex].Render(shader);
Sections[section].Render(TransformsCount);
}
_vao.Unbind();
if (IsSelected)
{
GL.StencilFunc(StencilFunction.Always, 0, 0xFF);
GL.Disable(EnableCap.StencilTest);
}
}
public void SimpleRender(Shader shader)
{
_vao.Bind();
shader.SetUniform("uMorphTime", MorphTime);
for (int section = 0; section < Sections.Length; section++)
{
Sections[section].Render(TransformsCount);
}
_vao.Unbind();
}
public void Outline(Shader shader)
{
GL.Enable(EnableCap.StencilTest);
GL.Disable(EnableCap.DepthTest);
GL.StencilFunc(StencilFunction.Notequal, 1, 0xFF);
_vao.Bind();
shader.SetUniform("uMorphTime", MorphTime);
GL.PolygonMode(MaterialFace.FrontAndBack, Wireframe ? PolygonMode.Line : PolygonMode.Fill);
for (int section = 0; section < Sections.Length; section++)
{
if (!Sections[section].Show) continue;
GL.DrawArraysInstancedBaseInstance(PrimitiveType.Triangles, Sections[section].FirstFaceIndex, Sections[section].FacesCount, TransformsCount, SelectedInstance);
}
_vao.Unbind();
GL.StencilFunc(StencilFunction.Always, 0, 0xFF);
GL.Enable(EnableCap.DepthTest);
GL.Disable(EnableCap.StencilTest);
}
public bool TrySave(out string label, out string savedFilePath)
{
var exportOptions = new ExporterOptions
{
TextureFormat = UserSettings.Default.TextureExportFormat,
LodFormat = UserSettings.Default.LodExportFormat,
MeshFormat = UserSettings.Default.MeshExportFormat,
Platform = UserSettings.Default.OverridedPlatform,
ExportMorphTargets = UserSettings.Default.SaveMorphTargets
};
var toSave = new Exporter(_export, exportOptions);
return toSave.TryWriteToDir(new DirectoryInfo(UserSettings.Default.ModelDirectory), out label, out savedFilePath);
}
public void Dispose()
{
_ebo.Dispose();
_vbo.Dispose();
_matrixVbo.Dispose();
_vao.Dispose();
if (HasMorphTargets)
{
_morphVbo.Dispose();
for (var morph = 0; morph < Morphs.Length; morph++)
{
Morphs[morph].Dispose();
}
}
for (int section = 0; section < Sections.Length; section++)
{
Sections[section].Dispose();
}
GL.DeleteProgram(_handle);
}
}

View File

@@ -0,0 +1,64 @@
using System;
using CUE4Parse.UE4.Assets.Exports.Animation;
using CUE4Parse.UE4.Objects.Core.Math;
using OpenTK.Graphics.OpenGL4;
namespace FModel.Views.Snooper;
public class Morph : IDisposable
{
private int _handle;
private readonly int _vertexSize = 3; // Position
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)
{
foreach (var vertex in morphTarget.MorphLODModels[0].Vertices)
{
if (vertex.SourceIdx == index)
{
positionDelta = vertex.PositionDelta;
return true;
}
}
positionDelta = 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))
{
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;
}
else
{
Vertices[baseIndex + count++] = vertices[i + 1];
Vertices[baseIndex + count++] = vertices[i + 2];
Vertices[baseIndex + count++] = vertices[i + 3];
}
}
}
public void Setup()
{
_handle = GL.CreateProgram();
}
public void Dispose()
{
GL.DeleteProgram(_handle);
}
}

View File

@@ -0,0 +1,188 @@
using System;
using System.Collections.Generic;
using CUE4Parse_Conversion.Textures;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Objects.Core.Misc;
using FModel.Settings;
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 readonly Dictionary<FGuid, Model> Models;
public readonly Dictionary<FGuid, Texture> Textures;
public readonly List<Light> Lights;
public readonly Dictionary<string, Texture> Icons;
private ETexturePlatform _platform;
private readonly string _game;
public Options()
{
Models = new Dictionary<FGuid, Model>();
Textures = new Dictionary<FGuid, Texture>();
Lights = new List<Light>();
Icons = new Dictionary<string, Texture>
{
["material"] = new ("materialicon"),
["noimage"] = new ("T_Placeholder_Item_Image"),
["pointlight"] = new ("pointlight"),
["spotlight"] = new ("spotlight"),
};
_platform = UserSettings.Default.OverridedPlatform;
_game = Services.ApplicationService.ApplicationView.CUE4Parse.Provider.GameName;
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 SelectSection(int index)
{
SelectedSection = index;
}
public void SelectMorph(int index, Model model)
{
SelectedMorph = index;
model.UpdateMorph(SelectedMorph);
}
public bool TryGetTexture(UTexture2D o, bool fix, out Texture texture)
{
var guid = o.LightingGuid;
if (!Textures.TryGetValue(guid, out texture) && o.GetFirstMip() is { } mip)
{
TextureDecoder.DecodeTexture(mip, o.Format, o.isNormalMap, _platform, out var data, out _);
if (fix) FixChannels(o, mip, ref data);
texture = new Texture(data, mip.SizeX, mip.SizeY, o);
Textures[guid] = texture;
}
return texture != null;
}
/// <summary>
/// Red : Specular
/// Blue : Roughness
/// Green : Metallic
/// </summary>
private void FixChannels(UTexture2D o, FTexture2DMipMap mip, ref byte[] data)
{
// only if it makes a big difference pls
switch (_game)
{
case "hk_project":
{
unsafe
{
var offset = 0;
fixed (byte* d = data)
{
for (var i = 0; i < mip.SizeX * mip.SizeY; i++)
{
(d[offset + 1], d[offset + 2]) = (d[offset + 2], d[offset + 1]); // swap G and B
offset += 4;
}
}
}
break;
}
}
}
public bool TryGetModel(out Model model) => Models.TryGetValue(SelectedModel, out model);
public bool TryGetModel(FGuid guid, out Model model) => Models.TryGetValue(guid, out model);
public bool 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(Model 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;
}
public void ResetModelsAndLights()
{
foreach (var model in Models.Values)
{
model.Dispose();
}
Models.Clear();
Lights.Clear();
}
public void Dispose()
{
ResetModelsAndLights();
foreach (var texture in Textures.Values)
{
texture.Dispose();
}
Textures.Clear();
foreach (var texture in Icons.Values)
{
texture.Dispose();
}
Icons.Clear();
}
}

View File

@@ -0,0 +1,118 @@
using System;
using System.Collections.Generic;
using CUE4Parse.UE4.Objects.Core.Misc;
using OpenTK.Graphics.OpenGL4;
using System.Numerics;
namespace FModel.Views.Snooper;
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,Model> models)
{
Bind();
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
_shader.Render(viewMatrix, projMatrix);
foreach ((FGuid guid, Model model) in models)
{
_shader.SetUniform("uA", guid.A);
_shader.SetUniform("uB", guid.B);
_shader.SetUniform("uC", guid.C);
_shader.SetUniform("uD", guid.D);
if (!model.Show) continue;
model.SimpleRender(_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 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);
}
}

View File

@@ -0,0 +1,41 @@
using System;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.Core.Misc;
using ImGuiNET;
namespace FModel.Views.Snooper;
public class PointLight : Light
{
public float Linear;
public float Quadratic;
public PointLight(FGuid model, Texture icon, UObject parent, UObject point, FVector position) : base(model, icon, parent, point, position)
{
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();
}
}

View File

@@ -0,0 +1,41 @@
using System;
using OpenTK.Graphics.OpenGL4;
namespace FModel.Views.Snooper;
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);
}
}

View File

@@ -0,0 +1,346 @@
using System;
using System.Numerics;
using System.Threading;
using System.Windows;
using CUE4Parse_Conversion.Animations;
using CUE4Parse_Conversion.Meshes;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Animation;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.Engine;
using CUE4Parse.UE4.Objects.UObject;
using FModel.Settings;
namespace FModel.Views.Snooper;
public class Renderer : IDisposable
{
private readonly Skybox _skybox;
private readonly Grid _grid;
private Shader _shader;
private Shader _outline;
private Shader _light;
public bool ShowSkybox;
public bool ShowGrid;
public bool ShowLights;
public int VertexColor;
public PickingTexture Picking { get; }
public Options Options { get; }
public Renderer(int width, int height)
{
_skybox = new Skybox();
_grid = new Grid();
Picking = new PickingTexture(width, height);
Options = new Options();
ShowSkybox = UserSettings.Default.ShowSkybox;
ShowGrid = UserSettings.Default.ShowGrid;
VertexColor = 0; // default
}
public Camera Load(CancellationToken cancellationToken, UObject export)
{
ShowLights = false;
return export switch
{
UStaticMesh st => LoadStaticMesh(st),
USkeletalMesh sk => LoadSkeletalMesh(sk),
UMaterialInstance mi => LoadMaterialInstance(mi),
UWorld wd => LoadWorld(cancellationToken, wd, Transform.Identity),
_ => throw new ArgumentOutOfRangeException(nameof(export))
};
}
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.NumTexCoords));
Options.SwapMaterial(false);
}
public void Animate(UAnimSequence animSequence)
{
if (!Options.TryGetModel(out var model) || !model.Skeleton.IsLoaded ||
model.Skeleton?.RefSkel.ConvertAnims(animSequence) is not { } anim || anim.Sequences.Count == 0)
return;
model.Skeleton.Anim = new Animation(anim);
Options.AnimateMesh(false);
}
public void Setup()
{
_skybox.Setup();
_grid.Setup();
_shader = new Shader();
_outline = new Shader("outline");
_light = new Shader("light");
Picking.Setup();
Options.SetupModelsAndLights();
}
public void Render(Camera cam)
{
var viewMatrix = cam.GetViewMatrix();
var projMatrix = cam.GetProjectionMatrix();
if (ShowSkybox) _skybox.Render(viewMatrix, projMatrix);
if (ShowGrid) _grid.Render(viewMatrix, projMatrix, cam.Near, cam.Far);
_shader.Render(viewMatrix, cam.Position, cam.Direction, projMatrix);
for (int i = 0; i < 6; i++)
_shader.SetUniform($"bVertexColors[{i}]", i == VertexColor);
// render model pass
foreach (var model in Options.Models.Values)
{
if (!model.Show) continue;
model.Render(_shader);
}
{ // 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);
}
// outline pass
if (Options.TryGetModel(out var selected) && selected.Show)
{
_outline.Render(viewMatrix, cam.Position, projMatrix);
selected.Outline(_outline);
}
// picking pass (dedicated FBO, binding to 0 afterward)
Picking.Render(viewMatrix, projMatrix, Options.Models);
}
private Camera SetupCamera(FBox box)
{
var far = box.Max.AbsMax();
var center = box.GetCenter();
return new Camera(
new Vector3(0f, center.Z, box.Max.Y * 3),
new Vector3(center.X, center.Z, center.Y),
0.01f, far * 50f, far / 1.5f);
}
private Camera LoadStaticMesh(UStaticMesh original)
{
var guid = original.LightingGuid;
if (Options.TryGetModel(guid, out var model))
{
model.AddInstance(Transform.Identity);
Application.Current.Dispatcher.Invoke(() => model.SetupInstances());
return null;
}
if (!original.TryConvert(out var mesh))
return null;
Options.Models[guid] = new Model(original, mesh);
Options.SelectModel(guid);
return SetupCamera(mesh.BoundingBox *= Constants.SCALE_DOWN_RATIO);
}
private Camera LoadSkeletalMesh(USkeletalMesh original)
{
var guid = Guid.NewGuid();
if (Options.Models.ContainsKey(guid) || !original.TryConvert(out var mesh)) return null;
Options.Models[guid] = new Model(original, mesh);
Options.SelectModel(guid);
return SetupCamera(mesh.BoundingBox *= Constants.SCALE_DOWN_RATIO);
}
private Camera LoadMaterialInstance(UMaterialInstance original)
{
var guid = Guid.NewGuid();
if (Options.Models.ContainsKey(guid)) return null;
Options.Models[guid] = new Cube(original);
Options.SelectModel(guid);
return SetupCamera(new FBox(new FVector(-.65f), new FVector(.65f)));
}
private Camera LoadWorld(CancellationToken cancellationToken, UWorld original, Transform transform)
{
var cam = new Camera(new Vector3(0f, 5f, 5f), Vector3.Zero, 0.01f, 1000f, 5f);
if (original.PersistentLevel.Load<ULevel>() is not { } persistentLevel)
return cam;
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" or "SplineMeshActor")
continue;
Services.ApplicationService.ApplicationView.Status.UpdateStatusLabel($"{original.Name} ... {i}/{length}");
WorldCamera(actor, ref cam);
// WorldLight(actor);
WorldMesh(actor, transform);
AdditionalWorlds(actor, transform.Matrix, cancellationToken);
}
Services.ApplicationService.ApplicationView.Status.UpdateStatusLabel($"{original.Name} ... {length}/{length}");
return cam;
}
private void WorldCamera(UObject actor, ref Camera cam)
{
if (actor.ExportType != "LevelBounds" || !actor.TryGetValue(out FPackageIndex boxComponent, "BoxComponent") ||
boxComponent.Load() is not { } boxObject) return;
var direction = boxObject.GetOrDefault("RelativeLocation", FVector.ZeroVector).ToMapVector() * Constants.SCALE_DOWN_RATIO;
var position = boxObject.GetOrDefault("RelativeScale3D", FVector.OneVector).ToMapVector() / 2f * Constants.SCALE_DOWN_RATIO;
var far = position.AbsMax();
cam = new Camera(
new Vector3(position.X, position.Y, position.Z),
new Vector3(direction.X, direction.Y, direction.Z),
0.01f, far * 25f, Math.Max(5f, far / 10f));
}
private void WorldLight(UObject actor)
{
// if (!actor.TryGetValue(out FPackageIndex lightComponent, "LightComponent") ||
// lightComponent.Load() is not { } lightObject) return;
//
// Cache.Lights.Add(new PointLight(Cache.Icons["pointlight"], lightObject, FVector.ZeroVector));
}
private void WorldMesh(UObject actor, Transform transform)
{
if (!actor.TryGetValue(out FPackageIndex staticMeshComponent, "StaticMeshComponent", "StaticMesh", "Mesh", "LightMesh") ||
staticMeshComponent.Load() is not { } staticMeshComp) return;
if (!staticMeshComp.TryGetValue(out FPackageIndex staticMesh, "StaticMesh") && actor.Class is UBlueprintGeneratedClass)
foreach (var actorExp in actor.Class.Owner.GetExports())
if (actorExp.TryGetValue(out staticMesh, "StaticMesh"))
break;
if (staticMesh?.Load() is not UStaticMesh m || m.Materials.Length < 1)
return;
var guid = m.LightingGuid;
var t = new Transform
{
Relation = transform.Matrix,
Position = staticMeshComp.GetOrDefault("RelativeLocation", FVector.ZeroVector).ToMapVector() * Constants.SCALE_DOWN_RATIO,
Rotation = staticMeshComp.GetOrDefault("RelativeRotation", FRotator.ZeroRotator),
Scale = staticMeshComp.GetOrDefault("RelativeScale3D", FVector.OneVector).ToMapVector()
};
if (Options.TryGetModel(guid, out var model))
{
model.AddInstance(t);
}
else if (m.TryConvert(out var mesh))
{
model = new Model(m, mesh, t);
if (actor.TryGetAllValues(out FPackageIndex[] textureData, "TextureData"))
{
for (int j = 0; j < textureData.Length; j++)
{
if (!model.Materials[model.Sections[j].MaterialIndex].IsUsed ||
textureData[j].Load() is not { } textureDataIdx)
continue;
if (textureDataIdx.TryGetValue(out FPackageIndex overrideMaterial, "OverrideMaterial") &&
overrideMaterial.TryLoad(out var material) && material is UMaterialInterface unrealMaterial)
model.Materials[model.Sections[j].MaterialIndex].SwapMaterial(unrealMaterial);
if (textureDataIdx.TryGetValue(out FPackageIndex diffuse, "Diffuse") &&
diffuse.Load() is UTexture2D diffuseTexture)
model.Materials[model.Sections[j].MaterialIndex].Parameters.Textures[CMaterialParams2.Diffuse[0][0]] = diffuseTexture;
if (textureDataIdx.TryGetValue(out FPackageIndex normal, "Normal") &&
normal.Load() is UTexture2D normalTexture)
model.Materials[model.Sections[j].MaterialIndex].Parameters.Textures[CMaterialParams2.Normals[0][0]] = normalTexture;
if (textureDataIdx.TryGetValue(out FPackageIndex specular, "Specular") &&
specular.Load() is UTexture2D specularTexture)
model.Materials[model.Sections[j].MaterialIndex].Parameters.Textures[CMaterialParams2.SpecularMasks[0][0]] = specularTexture;
}
}
if (staticMeshComp.TryGetValue(out FPackageIndex[] overrideMaterials, "OverrideMaterials"))
{
var max = model.Sections.Length - 1;
for (var j = 0; j < overrideMaterials.Length; j++)
{
if (j > max) break;
if (!model.Materials[model.Sections[j].MaterialIndex].IsUsed ||
overrideMaterials[j].Load() is not UMaterialInterface unrealMaterial) continue;
model.Materials[model.Sections[j].MaterialIndex].SwapMaterial(unrealMaterial);
}
}
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, t.Position));
}
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, t.Position));
}
}
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).ToMapVector() * Constants.SCALE_DOWN_RATIO,
Rotation = staticMeshComp.GetOrDefault("RelativeRotation", FRotator.ZeroRotator),
Scale = FVector.OneVector.ToMapVector()
};
for (int j = 0; j < additionalWorlds.Length; j++)
if (Creator.Utils.TryLoadObject(additionalWorlds[j].AssetPathName.Text, out UWorld w))
LoadWorld(cancellationToken, w, transform);
}
public void Save()
{
UserSettings.Default.ShowSkybox = ShowSkybox;
UserSettings.Default.ShowGrid = ShowGrid;
}
public void Dispose()
{
_skybox?.Dispose();
_grid?.Dispose();
_shader?.Dispose();
_outline?.Dispose();
_light?.Dispose();
Picking?.Dispose();
Options?.Dispose();
}
}

View File

@@ -0,0 +1,44 @@
using System;
using OpenTK.Graphics.OpenGL4;
namespace FModel.Views.Snooper;
public class Section : IDisposable
{
private int _handle;
public readonly int MaterialIndex;
public readonly int FacesCount;
public readonly int FirstFaceIndex;
public bool Show;
public Section(int index, int facesCount, int firstFaceIndex)
{
MaterialIndex = index;
FacesCount = facesCount;
FirstFaceIndex = firstFaceIndex;
Show = true;
}
public Section(int index, int facesCount, int firstFaceIndex, Material material) : this(index, facesCount, firstFaceIndex)
{
material.IsUsed = true;
Show = !material.Parameters.IsNull && !material.Parameters.IsTransparent;
}
public void Setup()
{
_handle = GL.CreateProgram();
}
public void Render(int instanceCount)
{
if (Show) GL.DrawArraysInstanced(PrimitiveType.Triangles, FirstFaceIndex, FacesCount, instanceCount);
}
public void Dispose()
{
GL.DeleteProgram(_handle);
}
}

View File

@@ -0,0 +1,135 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Numerics;
using System.Reflection;
using OpenTK.Graphics.OpenGL4;
namespace FModel.Views.Snooper;
public class Shader : IDisposable
{
private readonly int _handle;
private readonly Dictionary<string, int> _uniformsLocation = new ();
public Shader() : this("default") {}
public Shader(string name)
{
_handle = GL.CreateProgram();
var v = LoadShader(ShaderType.VertexShader, $"{name}.vert");
var f = LoadShader(ShaderType.FragmentShader, $"{name}.frag");
GL.AttachShader(_handle, v);
GL.AttachShader(_handle, f);
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)}");
}
GL.DetachShader(_handle, v);
GL.DetachShader(_handle, f);
GL.DeleteShader(v);
GL.DeleteShader(f);
}
private int LoadShader(ShaderType type, string file)
{
var executingAssembly = Assembly.GetExecutingAssembly();
using var stream = executingAssembly.GetManifestResourceStream($"{executingAssembly.GetName().Name}.Resources.{file}");
using var reader = new StreamReader(stream);
var handle = GL.CreateShader(type);
GL.ShaderSource(handle, reader.ReadToEnd());
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, Vector3 viewDir, Matrix4x4 projMatrix)
{
Render(viewMatrix, viewPos, projMatrix);
SetUniform("uViewDir", viewDir);
}
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()
{
GL.DeleteProgram(_handle);
}
}

View File

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

View File

@@ -0,0 +1,112 @@
using System;
using System.Numerics;
using OpenTK.Graphics.OpenGL4;
namespace FModel.Views.Snooper;
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.DepthFunc(DepthFunction.Lequal);
_vao.Bind();
_cubeMap.Bind(TextureUnit.Texture0);
_shader.Use();
viewMatrix.M41 = 0;
viewMatrix.M42 = 0;
viewMatrix.M43 = 0;
_shader.SetUniform("uView", viewMatrix);
_shader.SetUniform("uProjection", projMatrix);
_shader.SetUniform("cubemap", 0);
GL.DrawArrays(PrimitiveType.Triangles, 0, 36);
GL.DepthFunc(DepthFunction.Less);
}
public void Dispose()
{
_ebo?.Dispose();
_vbo?.Dispose();
_vao?.Dispose();
_shader?.Dispose();
GL.DeleteProgram(_handle);
}
}

View File

@@ -0,0 +1,835 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using CUE4Parse.UE4.Objects.Core.Misc;
using FModel.Framework;
using ImGuiNET;
using OpenTK.Windowing.Common;
using System.Numerics;
using FModel.Settings;
using OpenTK.Graphics.OpenGL4;
namespace FModel.Views.Snooper;
public class Swap
{
public bool Value;
public bool IsAware;
public void Reset()
{
Value = false;
}
}
public class Save
{
public bool Value;
public string Label;
public string Path;
public void Reset()
{
Value = false;
Label = string.Empty;
Path = string.Empty;
}
}
public class SnimGui
{
public readonly ImGuiController Controller;
private readonly Swap _swapper = new ();
private readonly Save _saver = new ();
private readonly string _renderer;
private readonly string _version;
private bool _viewportFocus;
private readonly Vector4 _accentColor = new (0.125f, 0.42f, 0.831f, 1.0f);
private readonly Vector4 _alertColor = new (0.831f, 0.573f, 0.125f, 1.0f);
private readonly Vector4 _errorColor = new (0.761f, 0.169f, 0.169f, 1.0f);
private const uint _dockspaceId = 1337;
public SnimGui(int width, int height)
{
_renderer = GL.GetString(StringName.Renderer);
_version = "OpenGL " + GL.GetString(StringName.Version);
Controller = new ImGuiController(width, height);
Theme();
}
public void Render(Snooper s)
{
Controller.SemiBold();
DrawDockSpace(s.Size);
SectionWindow("Material Inspector", s.Renderer, DrawMaterialInspector, false);
// Window("Timeline", () => {});
Window("World", () => DrawWorld(s), false);
Window("Sockets", () => DrawSockets(s));
DrawOuliner(s);
DrawDetails(s);
Draw3DViewport(s);
DrawNavbar();
Controller.Render();
}
private void DrawWorld(Snooper s)
{
if (ImGui.BeginTable("world_details", 2, ImGuiTableFlags.SizingStretchProp))
{
var length = s.Renderer.Options.Models.Count;
Layout("Renderer");ImGui.Text($" : {_renderer}");
Layout("Version");ImGui.Text($" : {_version}");
Layout("Loaded Models");ImGui.Text($" : x{length}");ImGui.SameLine();
var b = false;
if (ImGui.SmallButton("Save All"))
{
foreach (var model in s.Renderer.Options.Models.Values)
{
b |= model.TrySave(out _, out _);
}
}
Modal("Saved", b, () =>
{
ImGui.TextWrapped($"Successfully saved {length} models");
ImGui.Separator();
var size = new Vector2(120, 0);
if (ImGui.Button("OK", size))
{
ImGui.CloseCurrentPopup();
}
ImGui.SetItemDefaultFocus();
ImGui.SameLine();
if (ImGui.Button("Show In Explorer", size))
{
Process.Start("explorer.exe", $"/select, \"{UserSettings.Default.ModelDirectory.Replace('/', '\\')}\"");
ImGui.CloseCurrentPopup();
}
});
ImGui.EndTable();
}
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
if (ImGui.CollapsingHeader("Editor"))
{
if (ImGui.BeginTable("world_editor", 2))
{
Layout("Skybox");ImGui.PushID(1);
ImGui.Checkbox("", ref s.Renderer.ShowSkybox);
ImGui.PopID();Layout("Grid");ImGui.PushID(2);
ImGui.Checkbox("", ref s.Renderer.ShowGrid);
ImGui.PopID();Layout("Lights");ImGui.PushID(3);
ImGui.Checkbox("", ref s.Renderer.ShowLights);
ImGui.PopID();Layout("Vertex Colors");ImGui.PushID(4);
ImGui.Combo("vertex_colors", ref s.Renderer.VertexColor,
"Default\0Diffuse Only\0Colors\0Normals\0Tangent\0Texture Coordinates\0");
ImGui.PopID();
ImGui.EndTable();
}
}
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
if (ImGui.CollapsingHeader("Camera"))
{
s.Camera.ImGuiCamera();
}
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
if (ImGui.CollapsingHeader("Lights"))
{
for (int i = 0; i < s.Renderer.Options.Lights.Count; i++)
{
var id = $"[{i}] {s.Renderer.Options.Models[s.Renderer.Options.Lights[i].Model].Name}";
if (ImGui.TreeNode(id) && ImGui.BeginTable(id, 2))
{
s.Renderer.Options.SelectModel(s.Renderer.Options.Lights[i].Model);
s.Renderer.Options.Lights[i].ImGuiLight();
ImGui.EndTable();
ImGui.TreePop();
}
}
}
}
private void DrawDockSpace(OpenTK.Mathematics.Vector2i size)
{
const ImGuiWindowFlags flags =
ImGuiWindowFlags.MenuBar | ImGuiWindowFlags.NoDocking |
ImGuiWindowFlags.NoTitleBar | ImGuiWindowFlags.NoResize |
ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoMove |
ImGuiWindowFlags.NoBringToFrontOnFocus | ImGuiWindowFlags.NoNavFocus;
ImGui.SetNextWindowPos(new Vector2(0, 0));
ImGui.SetNextWindowSize(new Vector2(size.X, size.Y));
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
ImGui.Begin("Oui oui", flags);
ImGui.PopStyleVar();
ImGui.DockSpace(_dockspaceId);
}
private void DrawNavbar()
{
if (!ImGui.BeginMainMenuBar()) return;
Modal("Commands", ImGui.MenuItem("Commands"), () =>
{
ImGui.TextWrapped(
@"Most commands should be pretty straightforward but just in case here is a non-exhaustive list of things you can do in this 3D viewer:
1. UI / UX
- Press Shift while moving a window to dock it
- Ctrl Click in a box to input a new value
- Mouse Click + Drag in a box to modify the value without having to type
- Press H to hide the window and append the next mesh you extract
2. Viewport
- WASD to move around
- Shift to move faster
- XC to zoom
- Left Mouse Button pressed to look around
- Right Click to select a model in the world
3. Outliner
3.1. Right Click Model
- Show / Hide the model
- Show a skeletal representation of the model
- Save to save the model as .psk / .pskx
- Animate to load an animation on the model
- Teleport to quickly move the camera to the position of the model
- Delete
- Deselect
- Copy Name to Clipboard
4. World
- Save All to save all loaded models at once
(no it's not dying it's just freezing while saving them all)
5. Details
5.1. Right Click Section
- Show / Hide the section
- Swap to change the material used by this section
- Copy Name to Clipboard
5.2. Transform
- Move / Rotate / Scale the model in the world
5.3. Morph Targets
- Modify the vertices position by a given amount to change the shape of the model
");
ImGui.Separator();
var size = new Vector2(120, 0);
ImGui.InvisibleButton("", size * 3);
ImGui.SameLine();
ImGui.SetItemDefaultFocus();
if (ImGui.Button("OK", size))
{
ImGui.CloseCurrentPopup();
}
});
Modal("About Snooper", ImGui.MenuItem("About"), () =>
{
ImGui.TextWrapped(
@"WROTE AT 3AM FOR THE SAKE OF TESTING AN ABOUT MODAL
Snooper, an ""OpenGL x ImGui"" based 3D viewer, is the result of months of work in order to improve our last one and open up the capabilities data-mining offers. For too long, softwares including FModel were only focused on a bare minimum level of detail showed to the end-user. This is the first step of a long and painful transition to make FModel a viable open-source tool to deep dive into Unreal Engine, its structure, and show how things work internally.
Snooper aims to give an accurate preview of models, materials, animations, particles, levels, and sequences (oof) while keeping it compatible with most UE games. This is not an easy task AT ALL, in fact, I don't really know if everything will make out, but what I can say is that we have ideas and a vision for the future of FModel.
hello world!
");
ImGui.Separator();
var size = new Vector2(120, 0);
ImGui.InvisibleButton("", size * 4);
ImGui.SameLine();
ImGui.SetItemDefaultFocus();
if (ImGui.Button("OK", size))
{
ImGui.CloseCurrentPopup();
}
});
const string text = "Press H to Hide or ESC to Exit...";
ImGui.SetCursorPosX(ImGui.GetWindowViewport().WorkSize.X - ImGui.CalcTextSize(text).X - 5);
ImGui.TextColored(new Vector4(0.36f, 0.42f, 0.47f, 1.00f), text);
ImGui.EndMainMenuBar();
}
private void DrawOuliner(Snooper s)
{
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
Window("Outliner", () =>
{
if (ImGui.BeginTable("Items", 3, ImGuiTableFlags.Resizable | ImGuiTableFlags.BordersOuterV, ImGui.GetContentRegionAvail()))
{
ImGui.TableSetupColumn("Instance", ImGuiTableColumnFlags.NoHeaderWidth | ImGuiTableColumnFlags.WidthFixed);
ImGui.TableSetupColumn("Channels", ImGuiTableColumnFlags.WidthFixed);
ImGui.TableSetupColumn("Name");
ImGui.TableHeadersRow();
var i = 0;
foreach ((FGuid guid, Model model) in s.Renderer.Options.Models)
{
ImGui.PushID(i);
ImGui.TableNextRow();
ImGui.TableNextColumn();
if (!model.Show)
{
ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, ImGui.GetColorU32(new Vector4(1, 0, 0, .5f)));
}
ImGui.Text(model.TransformsCount.ToString("D"));
ImGui.TableNextColumn();
ImGui.Text(model.NumTexCoords.ToString("D"));
ImGui.TableNextColumn();
if (ImGui.Selectable(model.Name, s.Renderer.Options.SelectedModel == guid, ImGuiSelectableFlags.SpanAllColumns))
{
s.Renderer.Options.SelectModel(guid);
}
Popup(() =>
{
s.Renderer.Options.SelectModel(guid);
if (ImGui.MenuItem("Show", null, model.Show)) model.Show = !model.Show;
if (ImGui.MenuItem("Wireframe", null, model.Wireframe)) model.Wireframe = !model.Wireframe;
ImGui.Separator();
if (ImGui.Selectable("Save"))
{
s.WindowShouldFreeze(true);
_saver.Value = model.TrySave(out _saver.Label, out _saver.Path);
s.WindowShouldFreeze(false);
}
ImGui.BeginDisabled(true);
// ImGui.BeginDisabled(!model.HasSkeleton);
if (ImGui.Selectable("Animate"))
{
s.Renderer.Options.AnimateMesh(true);
s.WindowShouldClose(true, false);
}
ImGui.EndDisabled();
if (ImGui.Selectable("Teleport To"))
{
var instancePos = model.Transforms[model.SelectedInstance].Position;
s.Camera.Position = new Vector3(instancePos.X, instancePos.Y, instancePos.Z);
}
if (ImGui.Selectable("Delete")) s.Renderer.Options.Models.Remove(guid);
if (ImGui.Selectable("Deselect")) s.Renderer.Options.SelectModel(Guid.Empty);
ImGui.Separator();
if (ImGui.Selectable("Copy Name to Clipboard")) ImGui.SetClipboardText(model.Name);
});
ImGui.PopID();
i++;
}
Modal("Saved", _saver.Value, () =>
{
ImGui.TextWrapped($"Successfully saved {_saver.Label}");
ImGui.Separator();
var size = new Vector2(120, 0);
if (ImGui.Button("OK", size))
{
_saver.Reset();
ImGui.CloseCurrentPopup();
}
ImGui.SetItemDefaultFocus();
ImGui.SameLine();
if (ImGui.Button("Show In Explorer", size))
{
Process.Start("explorer.exe", $"/select, \"{_saver.Path.Replace('/', '\\')}\"");
_saver.Reset();
ImGui.CloseCurrentPopup();
}
});
ImGui.EndTable();
}
});
ImGui.PopStyleVar();
}
private void DrawSockets(Snooper s)
{
foreach (var model in s.Renderer.Options.Models.Values)
{
if (!model.HasSkeleton || model.IsSelected) return;
if (ImGui.TreeNode($"{model.Name} [{model.Skeleton.Sockets.Length}]"))
{
var i = 0;
foreach (var socket in model.Skeleton.Sockets)
{
ImGui.PushID(i);
ImGui.Text($"{socket.Name} attached to {socket.Bone}");
ImGui.Text($"P: {socket.Transform.Matrix.M41} | {socket.Transform.Matrix.M42} | {socket.Transform.Matrix.M43}");
// ImGui.Text($"R: {socket.Transform.Rotation}");
// ImGui.Text($"S: {socket.Transform.Scale}");
if (ImGui.Button("Attach") && s.Renderer.Options.TryGetModel(out var selected))
{
selected.Transforms[selected.SelectedInstance] = socket.Transform;
selected.UpdateMatrix(selected.SelectedInstance);
}
ImGui.PopID();
i++;
}
ImGui.TreePop();
}
}
}
private void DrawDetails(Snooper s)
{
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
MeshWindow("Details", s.Renderer, (icons, model) =>
{
if (ImGui.BeginTable("model_details", 2, ImGuiTableFlags.SizingStretchProp))
{
Layout("Entity");ImGui.Text($" : ({model.Type}) {model.Name}");
Layout("Guid");ImGui.Text($" : {s.Renderer.Options.SelectedModel.ToString(EGuidFormats.UniqueObjectGuid)}");
if (model.HasSkeleton)
{
Layout("Skeleton");ImGui.Text($" : {model.Skeleton.RefSkel.Name}");
Layout("Bones");ImGui.Text($" : x{model.Skeleton.RefSkel.BoneTree.Length}");
Layout("Sockets");ImGui.Text($" : x{model.Skeleton.Sockets.Length}");
}
ImGui.EndTable();
}
if (ImGui.BeginTabBar("tabbar_details", ImGuiTabBarFlags.None))
{
if (ImGui.BeginTabItem("Sections") && ImGui.BeginTable("table_sections", 2, ImGuiTableFlags.Resizable | ImGuiTableFlags.BordersOuterV, ImGui.GetContentRegionAvail()))
{
ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed);
ImGui.TableSetupColumn("Material");
ImGui.TableHeadersRow();
for (var i = 0; i < model.Sections.Length; i++)
{
var section = model.Sections[i];
var material = model.Materials[section.MaterialIndex];
ImGui.PushID(i);
ImGui.TableNextRow();
ImGui.TableNextColumn();
if (!section.Show)
{
ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, ImGui.GetColorU32(new Vector4(1, 0, 0, .5f)));
}
ImGui.Text(section.MaterialIndex.ToString("D"));
ImGui.TableNextColumn();
if (ImGui.Selectable(material.Name, s.Renderer.Options.SelectedSection == i, ImGuiSelectableFlags.SpanAllColumns))
{
s.Renderer.Options.SelectSection(i);
}
Popup(() =>
{
s.Renderer.Options.SelectSection(i);
if (ImGui.MenuItem("Show", null, section.Show)) section.Show = !section.Show;
if (ImGui.Selectable("Swap"))
{
if (_swapper.IsAware)
{
s.Renderer.Options.SwapMaterial(true);
s.WindowShouldClose(true, false);
}
else _swapper.Value = true;
}
ImGui.Separator();
if (ImGui.Selectable("Copy Name to Clipboard")) ImGui.SetClipboardText(material.Name);
});
ImGui.PopID();
}
ImGui.EndTable();
Modal("Swap?", _swapper.Value, () =>
{
ImGui.TextWrapped("You're about to swap a material.\nThe window will close for you to extract a material!\n\n");
ImGui.Separator();
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero);
ImGui.Checkbox("Got it! Don't show me again", ref _swapper.IsAware);
ImGui.PopStyleVar();
var size = new Vector2(120, 0);
if (ImGui.Button("OK", size))
{
_swapper.Reset();
s.Renderer.Options.SwapMaterial(true);
ImGui.CloseCurrentPopup();
s.WindowShouldClose(true, false);
}
ImGui.SetItemDefaultFocus();
ImGui.SameLine();
if (ImGui.Button("Cancel", size))
{
_swapper.Reset();
ImGui.CloseCurrentPopup();
}
});
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem("Transform"))
{
ImGui.PushID(0); ImGui.BeginDisabled(model.TransformsCount < 2);
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
ImGui.SliderInt("", ref model.SelectedInstance, 0, model.TransformsCount - 1, "Instance %i", ImGuiSliderFlags.AlwaysClamp);
ImGui.EndDisabled(); ImGui.PopID();
model.Transforms[model.SelectedInstance].ImGuiTransform(s.Camera.Speed / 100f);
model.UpdateMatrix(model.SelectedInstance);
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem("Morph Targets"))
{
if (model.HasMorphTargets)
{
const float width = 10;
var region = ImGui.GetContentRegionAvail();
var box = new Vector2(region.X - width, region.Y / 1.5f);
if (ImGui.BeginListBox("", box))
{
for (int i = 0; i < model.Morphs.Length; i++)
{
ImGui.PushID(i);
if (ImGui.Selectable(model.Morphs[i].Name, s.Renderer.Options.SelectedMorph == i))
{
s.Renderer.Options.SelectMorph(i, model);
}
ImGui.PopID();
}
ImGui.EndListBox();
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(2f, 0f));
ImGui.SameLine(); ImGui.PushID(99);
ImGui.VSliderFloat("", box with { X = width }, ref model.MorphTime, 0.0f, 1.0f, "", ImGuiSliderFlags.AlwaysClamp);
ImGui.PopID(); ImGui.PopStyleVar();
ImGui.Spacing();
ImGui.Text($"Time: {model.MorphTime:P}%");
}
}
else CenteredTextColored(_errorColor, "Selected Mesh Has No Morph Targets");
ImGui.EndTabItem();
}
}
});
ImGui.PopStyleVar();
}
private void DrawMaterialInspector(Dictionary<string, Texture> icons, Model model, Section section)
{
var material = model.Materials[section.MaterialIndex];
ImGui.Spacing();
ImGui.Image(icons["material"].GetPointer(), new Vector2(24));
ImGui.SameLine(); ImGui.AlignTextToFramePadding(); ImGui.Text(material.Name);
ImGui.Spacing();
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
if (ImGui.CollapsingHeader("Parameters"))
{
material.ImGuiParameters();
}
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
if (ImGui.CollapsingHeader("Textures"))
{
material.ImGuiTextures(icons, model);
}
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
if (ImGui.CollapsingHeader("Properties"))
{
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
if (ImGui.TreeNode("Scalars"))
{
material.ImGuiDictionaries("scalars", material.Parameters.Scalars, true);
ImGui.TreePop();
}
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
if (ImGui.TreeNode("Switchs"))
{
material.ImGuiDictionaries("switchs", material.Parameters.Switchs, true);
ImGui.TreePop();
}
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
if (ImGui.TreeNode("Colors"))
{
material.ImGuiColors(material.Parameters.Colors);
ImGui.TreePop();
}
if (ImGui.TreeNode("Referenced Textures"))
{
material.ImGuiDictionaries("textures", material.Parameters.Textures);
ImGui.TreePop();
}
}
}
private void Draw3DViewport(Snooper s)
{
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
Window("3D Viewport", () =>
{
var largest = ImGui.GetContentRegionAvail();
largest.X -= ImGui.GetScrollX();
largest.Y -= ImGui.GetScrollY();
var size = new Vector2(largest.X, largest.Y);
s.Camera.AspectRatio = size.X / size.Y;
ImGui.ImageButton(s.Framebuffer.GetPointer(), size, new Vector2(0, 1), new Vector2(1, 0), 0);
if (ImGui.IsItemHovered())
{
// if left button down while mouse is hover viewport
if (ImGui.IsMouseDown(ImGuiMouseButton.Left) && !_viewportFocus)
{
_viewportFocus = true;
s.CursorState = CursorState.Grabbed;
}
if (ImGui.IsMouseClicked(ImGuiMouseButton.Right))
{
var guid = s.Renderer.Picking.ReadPixel(ImGui.GetMousePos(), ImGui.GetCursorScreenPos(), size);
s.Renderer.Options.SelectModel(guid);
ImGui.SetWindowFocus("Outliner");
}
}
const float lookSensitivity = 0.1f;
if (ImGui.IsMouseDragging(ImGuiMouseButton.Left, lookSensitivity) && _viewportFocus)
{
var io = ImGui.GetIO();
var delta = io.MouseDelta * lookSensitivity;
s.Camera.ModifyDirection(delta.X, delta.Y);
}
// if left button up and mouse was in viewport
if (ImGui.IsMouseReleased(ImGuiMouseButton.Left) && _viewportFocus)
{
_viewportFocus = false;
s.CursorState = CursorState.Normal;
}
float framerate = ImGui.GetIO().Framerate;
ImGui.SetCursorPos(size with { X = 7.5f });
ImGui.Text($"FPS: {framerate:0} ({1000.0f / framerate:0.##} ms)");
}, false);
ImGui.PopStyleVar();
}
private void Popup(Action content)
{
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(4f));
if (ImGui.BeginPopupContextItem())
{
content();
ImGui.EndPopup();
}
ImGui.PopStyleVar();
}
private void Modal(string title, bool condition, Action content)
{
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(4f));
var pOpen = true;
if (condition) ImGui.OpenPopup(title);
ImGui.SetNextWindowPos(ImGui.GetMainViewport().GetCenter(), ImGuiCond.Appearing, new Vector2(.5f));
if (ImGui.BeginPopupModal(title, ref pOpen, ImGuiWindowFlags.AlwaysAutoResize))
{
content();
ImGui.EndPopup();
}
ImGui.PopStyleVar();
}
private void Window(string name, Action content, bool styled = true)
{
if (ImGui.Begin(name, ImGuiWindowFlags.NoScrollbar))
{
Controller.PopFont();
if (styled) PushStyleCompact();
content();
if (styled) PopStyleCompact();
ImGui.End();
}
}
private void MeshWindow(string name, Renderer renderer, Action<Dictionary<string, Texture>, Model> content, bool styled = true)
{
Window(name, () =>
{
if (renderer.Options.TryGetModel(out var model)) content(renderer.Options.Icons, model);
else NoMeshSelected();
}, styled);
}
private void SectionWindow(string name, Renderer renderer, Action<Dictionary<string, Texture>, Model, Section> content, bool styled = true)
{
MeshWindow(name, renderer, (icons, model) =>
{
if (renderer.Options.TryGetSection(model, out var section)) content(icons, model, section);
else NoSectionSelected();
}, styled);
}
private void PopStyleCompact() => ImGui.PopStyleVar(2);
private void PushStyleCompact()
{
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, new Vector2(8, 3));
ImGui.PushStyleVar(ImGuiStyleVar.CellPadding, new Vector2(0, 1));
}
private void NoMeshSelected() => CenteredTextColored(_errorColor, "No Mesh Selected");
private void NoSectionSelected() => CenteredTextColored(_errorColor, "No Section Selected");
private void CenteredTextColored(Vector4 color, string text)
{
var region = ImGui.GetContentRegionAvail();
var size = ImGui.CalcTextSize(text);
ImGui.SetCursorPos(new Vector2(
ImGui.GetCursorPosX() + (region.X - size.X) / 2,
ImGui.GetCursorPosY() + (region.Y - size.Y) / 2));
Controller.Bold();
ImGui.TextColored(color, text);
Controller.PopFont();
}
public static void Layout(string name, bool tooltip = false)
{
ImGui.TableNextRow();
ImGui.TableSetColumnIndex(0);
ImGui.Spacing();ImGui.SameLine();ImGui.Text(name);
if (tooltip) TooltipCopy(name);
ImGui.TableSetColumnIndex(1);
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
}
public static void TooltipCopy(string name)
{
if (ImGui.IsItemHovered())
{
ImGui.BeginTooltip();
ImGui.Text(name);
ImGui.EndTooltip();
}
if (ImGui.IsItemClicked()) ImGui.SetClipboardText(name);
}
private void Theme()
{
var style = ImGui.GetStyle();
var io = ImGui.GetIO();
io.ConfigFlags |= ImGuiConfigFlags.DockingEnable;
io.ConfigFlags |= ImGuiConfigFlags.ViewportsEnable;
io.ConfigWindowsMoveFromTitleBarOnly = true;
io.ConfigDockingWithShift = true;
style.WindowPadding = new Vector2(4f);
style.FramePadding = new Vector2(3f);
style.CellPadding = new Vector2(3f, 2f);
style.ItemSpacing = new Vector2(6f, 3f);
style.ItemInnerSpacing = new Vector2(3f);
style.TouchExtraPadding = new Vector2(0f);
style.IndentSpacing = 20f;
style.ScrollbarSize = 10f;
style.GrabMinSize = 8f;
style.WindowBorderSize = 0f;
style.ChildBorderSize = 0f;
style.PopupBorderSize = 0f;
style.FrameBorderSize = 0f;
style.TabBorderSize = 0f;
style.WindowRounding = 0f;
style.ChildRounding = 0f;
style.FrameRounding = 0f;
style.PopupRounding = 0f;
style.ScrollbarRounding = 0f;
style.GrabRounding = 0f;
style.LogSliderDeadzone = 0f;
style.TabRounding = 0f;
style.WindowTitleAlign = new Vector2(0.5f);
style.WindowMenuButtonPosition = ImGuiDir.Right;
style.ColorButtonPosition = ImGuiDir.Right;
style.ButtonTextAlign = new Vector2(0.5f);
style.SelectableTextAlign = new Vector2(0f);
style.DisplaySafeAreaPadding = new Vector2(3f);
style.Colors[(int) ImGuiCol.Text] = new Vector4(1.00f, 1.00f, 1.00f, 1.00f);
style.Colors[(int) ImGuiCol.TextDisabled] = new Vector4(0.50f, 0.50f, 0.50f, 1.00f);
style.Colors[(int) ImGuiCol.WindowBg] = new Vector4(0.11f, 0.11f, 0.12f, 1.00f);
style.Colors[(int) ImGuiCol.ChildBg] = new Vector4(0.15f, 0.15f, 0.19f, 1.00f);
style.Colors[(int) ImGuiCol.PopupBg] = new Vector4(0.08f, 0.08f, 0.08f, 0.94f);
style.Colors[(int) ImGuiCol.Border] = new Vector4(0.25f, 0.26f, 0.33f, 1.00f);
style.Colors[(int) ImGuiCol.BorderShadow] = new Vector4(0.00f, 0.00f, 0.00f, 0.00f);
style.Colors[(int) ImGuiCol.FrameBg] = new Vector4(0.05f, 0.05f, 0.05f, 0.54f);
style.Colors[(int) ImGuiCol.FrameBgHovered] = new Vector4(0.69f, 0.69f, 1.00f, 0.20f);
style.Colors[(int) ImGuiCol.FrameBgActive] = new Vector4(0.69f, 0.69f, 1.00f, 0.39f);
style.Colors[(int) ImGuiCol.TitleBg] = new Vector4(0.09f, 0.09f, 0.09f, 1.00f);
style.Colors[(int) ImGuiCol.TitleBgActive] = new Vector4(0.09f, 0.09f, 0.09f, 1.00f);
style.Colors[(int) ImGuiCol.TitleBgCollapsed] = new Vector4(0.05f, 0.05f, 0.05f, 0.51f);
style.Colors[(int) ImGuiCol.MenuBarBg] = new Vector4(0.14f, 0.14f, 0.14f, 1.00f);
style.Colors[(int) ImGuiCol.ScrollbarBg] = new Vector4(0.02f, 0.02f, 0.02f, 0.53f);
style.Colors[(int) ImGuiCol.ScrollbarGrab] = new Vector4(0.31f, 0.31f, 0.31f, 1.00f);
style.Colors[(int) ImGuiCol.ScrollbarGrabHovered] = new Vector4(0.41f, 0.41f, 0.41f, 1.00f);
style.Colors[(int) ImGuiCol.ScrollbarGrabActive] = new Vector4(0.51f, 0.51f, 0.51f, 1.00f);
style.Colors[(int) ImGuiCol.CheckMark] = new Vector4(0.13f, 0.42f, 0.83f, 1.00f);
style.Colors[(int) ImGuiCol.SliderGrab] = new Vector4(0.13f, 0.42f, 0.83f, 0.78f);
style.Colors[(int) ImGuiCol.SliderGrabActive] = new Vector4(0.13f, 0.42f, 0.83f, 1.00f);
style.Colors[(int) ImGuiCol.Button] = new Vector4(0.05f, 0.05f, 0.05f, 0.54f);
style.Colors[(int) ImGuiCol.ButtonHovered] = new Vector4(0.69f, 0.69f, 1.00f, 0.20f);
style.Colors[(int) ImGuiCol.ButtonActive] = new Vector4(0.69f, 0.69f, 1.00f, 0.39f);
style.Colors[(int) ImGuiCol.Header] = new Vector4(0.05f, 0.26f, 0.56f, 1.00f);
style.Colors[(int) ImGuiCol.HeaderHovered] = new Vector4(0.05f, 0.26f, 0.56f, 0.39f);
style.Colors[(int) ImGuiCol.HeaderActive] = new Vector4(0.04f, 0.23f, 0.52f, 1.00f);
style.Colors[(int) ImGuiCol.Separator] = new Vector4(0.43f, 0.43f, 0.50f, 0.50f);
style.Colors[(int) ImGuiCol.SeparatorHovered] = new Vector4(0.10f, 0.40f, 0.75f, 0.78f);
style.Colors[(int) ImGuiCol.SeparatorActive] = new Vector4(0.10f, 0.40f, 0.75f, 1.00f);
style.Colors[(int) ImGuiCol.ResizeGrip] = new Vector4(0.13f, 0.42f, 0.83f, 0.39f);
style.Colors[(int) ImGuiCol.ResizeGripHovered] = new Vector4(0.12f, 0.41f, 0.81f, 0.78f);
style.Colors[(int) ImGuiCol.ResizeGripActive] = new Vector4(0.12f, 0.41f, 0.81f, 1.00f);
style.Colors[(int) ImGuiCol.Tab] = new Vector4(0.15f, 0.15f, 0.19f, 1.00f);
style.Colors[(int) ImGuiCol.TabHovered] = new Vector4(0.35f, 0.35f, 0.41f, 0.80f);
style.Colors[(int) ImGuiCol.TabActive] = new Vector4(0.23f, 0.24f, 0.29f, 1.00f);
style.Colors[(int) ImGuiCol.TabUnfocused] = new Vector4(0.15f, 0.15f, 0.15f, 1.00f);
style.Colors[(int) ImGuiCol.TabUnfocusedActive] = new Vector4(0.23f, 0.24f, 0.29f, 1.00f);
style.Colors[(int) ImGuiCol.DockingPreview] = new Vector4(0.26f, 0.59f, 0.98f, 0.70f);
style.Colors[(int) ImGuiCol.DockingEmptyBg] = new Vector4(0.20f, 0.20f, 0.20f, 1.00f);
style.Colors[(int) ImGuiCol.PlotLines] = new Vector4(0.61f, 0.61f, 0.61f, 1.00f);
style.Colors[(int) ImGuiCol.PlotLinesHovered] = new Vector4(1.00f, 0.43f, 0.35f, 1.00f);
style.Colors[(int) ImGuiCol.PlotHistogram] = new Vector4(0.90f, 0.70f, 0.00f, 1.00f);
style.Colors[(int) ImGuiCol.PlotHistogramHovered] = new Vector4(1.00f, 0.60f, 0.00f, 1.00f);
style.Colors[(int) ImGuiCol.TableHeaderBg] = new Vector4(0.09f, 0.09f, 0.09f, 1.00f);
style.Colors[(int) ImGuiCol.TableBorderStrong] = new Vector4(0.69f, 0.69f, 1.00f, 0.20f);
style.Colors[(int) ImGuiCol.TableBorderLight] = new Vector4(0.69f, 0.69f, 1.00f, 0.20f);
style.Colors[(int) ImGuiCol.TableRowBg] = new Vector4(0.00f, 0.00f, 0.00f, 0.00f);
style.Colors[(int) ImGuiCol.TableRowBgAlt] = new Vector4(1.00f, 1.00f, 1.00f, 0.06f);
style.Colors[(int) ImGuiCol.TextSelectedBg] = new Vector4(0.26f, 0.59f, 0.98f, 0.35f);
style.Colors[(int) ImGuiCol.DragDropTarget] = new Vector4(1.00f, 1.00f, 0.00f, 0.90f);
style.Colors[(int) ImGuiCol.NavHighlight] = new Vector4(0.26f, 0.59f, 0.98f, 1.00f);
style.Colors[(int) ImGuiCol.NavWindowingHighlight] = new Vector4(1.00f, 1.00f, 1.00f, 0.70f);
style.Colors[(int) ImGuiCol.NavWindowingDimBg] = new Vector4(0.80f, 0.80f, 0.80f, 0.20f);
style.Colors[(int) ImGuiCol.ModalWindowDimBg] = new Vector4(0.80f, 0.80f, 0.80f, 0.35f);
}
}

View File

@@ -0,0 +1,185 @@
using System;
using System.ComponentModel;
using System.Numerics;
using System.Threading;
using System.Windows;
using CUE4Parse.UE4.Assets.Exports;
using ImGuiNET;
using OpenTK.Graphics.OpenGL4;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
using OpenTK.Windowing.GraphicsLibraryFramework;
namespace FModel.Views.Snooper;
public class Snooper : GameWindow
{
public Camera Camera;
public readonly FramebufferObject Framebuffer;
public readonly Renderer Renderer;
private readonly SnimGui _gui;
private float _previousSpeed;
private bool _init;
public Snooper(GameWindowSettings gwSettings, NativeWindowSettings nwSettings) : base(gwSettings, nwSettings)
{
Camera = new Camera();
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 export)
{
var newCamera = Renderer.Load(cancellationToken, export) ?? new Camera();
if (newCamera.Speed > _previousSpeed)
{
Camera = newCamera;
_previousSpeed = Camera.Speed;
}
return Renderer.Options.Models.Count > 0;
}
public unsafe void WindowShouldClose(bool value, bool clear)
{
if (clear)
{
_previousSpeed = 0f;
Renderer.Options.ResetModelsAndLights();
Renderer.Options.SelectModel(Guid.Empty);
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()
{
Application.Current.Dispatcher.Invoke(delegate
{
WindowShouldClose(false, false);
base.Run();
});
}
protected override void OnLoad()
{
if (_init)
{
Renderer.Options.SetupModelsAndLights();
return;
}
base.OnLoad();
CenterWindow();
GL.ClearColor(OpenTK.Mathematics.Color4.Black);
GL.Enable(EnableCap.Blend);
GL.Enable(EnableCap.DepthTest);
GL.Enable(EnableCap.Multisample);
GL.StencilOp(StencilOp.Keep, StencilOp.Replace, StencilOp.Replace);
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
Framebuffer.Setup();
Renderer.Setup();
_init = true;
}
protected override void OnRenderFrame(FrameEventArgs args)
{
base.OnRenderFrame(args);
if (!IsVisible)
return;
ClearWhatHasBeenDrawn(); // clear window background
_gui.Controller.Update(this, (float)args.Time);
_gui.Render(this);
Framebuffer.Bind(); // switch to viewport background
ClearWhatHasBeenDrawn(); // clear viewport background
Renderer.Render(Camera);
Framebuffer.BindMsaa();
Framebuffer.Bind(0); // switch to window background
SwapBuffers();
}
private void ClearWhatHasBeenDrawn()
{
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit);
GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill);
}
protected override void OnTextInput(TextInputEventArgs e)
{
base.OnTextInput(e);
if (!IsVisible)
return;
_gui.Controller.PressChar((char) e.Unicode);
}
protected override void OnUpdateFrame(FrameEventArgs e)
{
base.OnUpdateFrame(e);
if (!IsVisible || ImGui.GetIO().WantTextInput)
return;
var multiplier = KeyboardState.IsKeyDown(Keys.LeftShift) ? 2f : 1f;
var moveSpeed = Camera.Speed * multiplier * (float) e.Time;
if (KeyboardState.IsKeyDown(Keys.W))
Camera.Position += moveSpeed * Camera.Direction;
if (KeyboardState.IsKeyDown(Keys.S))
Camera.Position -= moveSpeed * Camera.Direction;
if (KeyboardState.IsKeyDown(Keys.A))
Camera.Position -= Vector3.Normalize(Vector3.Cross(Camera.Direction, Camera.Up)) * moveSpeed;
if (KeyboardState.IsKeyDown(Keys.D))
Camera.Position += Vector3.Normalize(Vector3.Cross(Camera.Direction, Camera.Up)) * moveSpeed;
if (KeyboardState.IsKeyDown(Keys.E))
Camera.Position += moveSpeed * Camera.Up;
if (KeyboardState.IsKeyDown(Keys.Q))
Camera.Position -= moveSpeed * Camera.Up;
if (KeyboardState.IsKeyDown(Keys.X))
Camera.ModifyZoom(-.5f);
if (KeyboardState.IsKeyDown(Keys.C))
Camera.ModifyZoom(+.5f);
if (KeyboardState.IsKeyPressed(Keys.H))
WindowShouldClose(true, false);
if (KeyboardState.IsKeyPressed(Keys.Escape))
WindowShouldClose(true, true);
}
protected override void OnResize(ResizeEventArgs e)
{
base.OnResize(e);
GL.Viewport(0, 0, e.Width, e.Height);
Camera.AspectRatio = e.Width / (float) e.Height;
Framebuffer.WindowResized(e.Width, e.Height);
Renderer.Picking.WindowResized(e.Width, e.Height);
_gui.Controller.WindowResized(e.Width, e.Height);
}
protected override void OnClosing(CancelEventArgs e)
{
base.OnClosing(e);
WindowShouldClose(true, true);
}
}

View File

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

View File

@@ -0,0 +1,50 @@
using System;
using System.Numerics;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.Core.Misc;
using ImGuiNET;
namespace FModel.Views.Snooper;
public class SpotLight : Light
{
public Vector2 Direction; // ???
public float Attenuation;
public float ConeAngle;
public SpotLight(FGuid model, Texture icon, UObject parent, UObject spot, FVector position) : base(model, icon, parent, spot, position)
{
if (!spot.TryGetValue(out Attenuation, "AttenuationRadius", "SourceRadius"))
Attenuation = 1.0f;
Attenuation *= Constants.SCALE_DOWN_RATIO;
Direction = Vector2.Zero;
ConeAngle = (spot.GetOrDefault("InnerConeAngle", 50.0f) + spot.GetOrDefault("OuterConeAngle", 60.0f)) / 2.0f;
ConeAngle = MathF.Cos(Helper.DegreesToRadians(ConeAngle));
}
public override void Render(int i, Shader shader)
{
base.Render(i, shader);
shader.SetUniform($"uLights[{i}].Direction", Direction);
shader.SetUniform($"uLights[{i}].Attenuation", Attenuation);
shader.SetUniform($"uLights[{i}].ConeAngle", ConeAngle);
shader.SetUniform($"uLights[{i}].Type", 1);
}
public override void ImGuiLight()
{
base.ImGuiLight();
SnimGui.Layout("Direction");ImGui.PushID(3);
ImGui.DragFloat2("", ref Direction, 0.01f);
ImGui.PopID();SnimGui.Layout("Attenuation");ImGui.PushID(4);
ImGui.DragFloat("", ref Attenuation, 0.1f);ImGui.PopID();
var angle = Helper.RadiansToDegrees(MathF.Acos(ConeAngle));
SnimGui.Layout("Cone Angle");ImGui.PushID(5);
ImGui.DragFloat("", ref angle, 0.1f, 0.0f, 90.0f, "%.1f°");ImGui.PopID();
ConeAngle = MathF.Cos(Helper.DegreesToRadians(angle));
}
}

View File

@@ -0,0 +1,206 @@
using System;
using System.Windows;
using CUE4Parse.UE4.Assets.Exports.Texture;
using CUE4Parse.UE4.Objects.Core.Math;
using OpenTK.Graphics.OpenGL4;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
namespace FModel.Views.Snooper;
public class Texture : IDisposable
{
private readonly int _handle;
private readonly TextureType _type;
private readonly TextureTarget _target;
public readonly string Type;
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;
public string Label;
public Texture(TextureType type)
{
_handle = GL.GenTexture();
_type = type;
_target = _type switch
{
TextureType.Cubemap => TextureTarget.TextureCubeMap,
TextureType.MsaaFramebuffer => TextureTarget.Texture2DMultisample,
_ => TextureTarget.Texture2D
};
Label = "(?) Click to Copy Path";
}
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.TexParameter(_target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.Nearest);
GL.TexParameter(_target, TextureParameterName.TextureMagFilter, (int) TextureMinFilter.Nearest);
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(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) TextureMinFilter.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(byte[] data, int width, int height, UTexture2D texture2D) : this(TextureType.Normal)
{
Type = texture2D.ExportType;
Name = texture2D.Name;
Path = texture2D.Owner?.Name;
Format = texture2D.Format;
ImportedWidth = texture2D.ImportedSize.X;
ImportedHeight = texture2D.ImportedSize.Y;
Width = width;
Height = height;
Bind(TextureUnit.Texture0);
GL.TexImage2D(_target, 0, texture2D.SRGB ? PixelInternalFormat.Srgb : PixelInternalFormat.Rgb, Width, Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, data);
GL.TexParameter(_target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.LinearMipmapLinear);
GL.TexParameter(_target, TextureParameterName.TextureMagFilter, (int) TextureMinFilter.Linear);
GL.TexParameter(_target, TextureParameterName.TextureBaseLevel, 0);
GL.TexParameter(_target, TextureParameterName.TextureMaxLevel, 8);
GL.GenerateMipmap(GenerateMipmapTarget.Texture2D);
}
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) TextureMinFilter.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.Linear);
GL.TexParameter(_target, TextureParameterName.TextureMagFilter, (int) TextureMinFilter.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);
}
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) TextureMinFilter.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 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);
}
}
public enum TextureType
{
Normal,
Cubemap,
Framebuffer,
MsaaFramebuffer
}

View File

@@ -0,0 +1,81 @@
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.ToMapVector();
public FRotator Rotation = new (0f);
public FVector Scale = FVector.OneVector.ToMapVector();
public Matrix4x4 Matrix =>
Matrix4x4.CreateScale(Scale) *
Matrix4x4.CreateRotationX(Helper.DegreesToRadians(Rotation.Roll)) *
Matrix4x4.CreateRotationY(Helper.DegreesToRadians(-Rotation.Yaw)) *
Matrix4x4.CreateRotationZ(Helper.DegreesToRadians(Rotation.Pitch)) *
Matrix4x4.CreateTranslation(Position) *
Relation;
public void ImGuiTransform(float speed)
{
const int width = 100;
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
if (ImGui.TreeNode("Location"))
{
ImGui.PushID(1);
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("X", ref Position.X, speed, 0f, 0f, "%.2f m");
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("Y", ref Position.Z, speed, 0f, 0f, "%.2f m");
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("Z", ref Position.Y, speed, 0f, 0f, "%.2f m");
ImGui.PopID();
ImGui.TreePop();
}
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
if (ImGui.TreeNode("Rotation"))
{
ImGui.PushID(2);
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("X", ref Rotation.Roll, .5f, 0f, 0f, "%.1f°");
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("Y", ref Rotation.Pitch, .5f, 0f, 0f, "%.1f°");
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("Z", ref Rotation.Yaw, .5f, 0f, 0f, "%.1f°");
ImGui.PopID();
ImGui.TreePop();
}
if (ImGui.TreeNode("Scale"))
{
ImGui.PushID(3);
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("X", ref Scale.X, speed, 0f, 0f, "%.3f");
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("Y", ref Scale.Z, speed, 0f, 0f, "%.3f");
ImGui.SetNextItemWidth(width);
ImGui.DragFloat("Z", ref Scale.Y, speed, 0f, 0f, "%.3f");
ImGui.PopID();
ImGui.TreePop();
}
}
}

View File

@@ -0,0 +1,70 @@
using System;
using System.Numerics;
using OpenTK.Graphics.OpenGL4;
namespace FModel.Views.Snooper;
public class VertexArrayObject<TVertexType, TIndexType> : IDisposable where TVertexType : unmanaged where TIndexType : unmanaged
{
private readonly int _handle;
public VertexArrayObject(BufferObject<TVertexType> vbo, BufferObject<TIndexType> ebo)
{
_handle = GL.GenVertexArray();
Bind();
vbo.Bind();
ebo.Bind();
}
public unsafe void VertexAttributePointer(uint index, int count, VertexAttribPointerType type, int vertexSize, int offset)
{
switch (type)
{
case VertexAttribPointerType.Int:
GL.VertexAttribIPointer(index, count, VertexAttribIntegerType.Int, vertexSize * sizeof(TVertexType), (IntPtr) (offset * sizeof(TVertexType)));
break;
default:
GL.VertexAttribPointer(index, count, type, false, vertexSize * sizeof(TVertexType), offset * sizeof(TVertexType));
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);
}
}