|
|
@ -1 +1 @@
|
|||
Subproject commit 5655c30f180a50bd07dc0282daace6b6edc9ea72
|
||||
Subproject commit 2d5e2bdd35db42759d8320076778737fcf9d53a2
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit 97174265894eddcb0d94ef570d36ddc559a8573a
|
||||
Subproject commit 21df8a55d474f14148a35bc943e06f3fdc20c997
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
using AdonisUI.Controls;
|
||||
using AdonisUI.Controls;
|
||||
using Microsoft.Win32;
|
||||
using Serilog;
|
||||
using System;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Reflection;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
|
||||
namespace FModel;
|
||||
|
|
@ -27,4 +28,19 @@ public static class Constants
|
|||
public const string _VAL_LIVE_TRIGGER = "valorant-live.manifest";
|
||||
|
||||
public const string _NO_PRESET_TRIGGER = "Hand Made";
|
||||
|
||||
public static int PALETTE_LENGTH => COLOR_PALETTE.Length;
|
||||
public static readonly Vector3[] COLOR_PALETTE =
|
||||
{
|
||||
new (0.231f, 0.231f, 0.231f), // Dark gray
|
||||
new (0.376f, 0.490f, 0.545f), // Teal
|
||||
new (0.957f, 0.263f, 0.212f), // Red
|
||||
new (0.196f, 0.804f, 0.196f), // Green
|
||||
new (0.957f, 0.647f, 0.212f), // Orange
|
||||
new (0.612f, 0.153f, 0.690f), // Purple
|
||||
new (0.129f, 0.588f, 0.953f), // Blue
|
||||
new (1.000f, 0.920f, 0.424f), // Yellow
|
||||
new (0.824f, 0.412f, 0.118f), // Brown
|
||||
new (0.612f, 0.800f, 0.922f) // Light blue
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,12 @@ public class BaseMaterialInstance : BaseIcon
|
|||
}
|
||||
|
||||
if (Preview == null)
|
||||
{
|
||||
if (material.TryGetValue(out FPackageIndex parent, "Parent"))
|
||||
Utils.TryGetPackageIndexExport(parent, out material);
|
||||
|
||||
goto texture_finding;
|
||||
}
|
||||
|
||||
foreach (var vectorParameter in material.VectorParameterValues)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -37,9 +37,9 @@ public class BaseUserControl : UCreator
|
|||
|
||||
public override void ParseForInfo()
|
||||
{
|
||||
if (Object.TryGetValue(out FText optionDisplayName, "OptionDisplayName"))
|
||||
if (Object.TryGetValue(out FText optionDisplayName, "OptionDisplayName", "OptionText"))
|
||||
DisplayName = optionDisplayName.Text.ToUpperInvariant();
|
||||
if (Object.TryGetValue(out FText optionDescription, "OptionDescription"))
|
||||
if (Object.TryGetValue(out FText optionDescription, "OptionDescription", "OptionToolTip"))
|
||||
{
|
||||
Description = optionDescription.Text;
|
||||
|
||||
|
|
@ -48,12 +48,12 @@ public class BaseUserControl : UCreator
|
|||
Height += (int) _descriptionPaint.TextSize;
|
||||
}
|
||||
|
||||
if (Object.TryGetValue(out FStructFallback[] optionValues, "OptionValues"))
|
||||
if (Object.TryGetValue(out FStructFallback[] optionValues, "OptionValues", "Options"))
|
||||
{
|
||||
_optionValues = new List<Options>();
|
||||
foreach (var option in optionValues)
|
||||
{
|
||||
if (option.TryGetValue(out FText displayName, "DisplayName"))
|
||||
if (option.TryGetValue(out FText displayName, "DisplayName", "DisplayText"))
|
||||
{
|
||||
var opt = new Options { Option = displayName.Text.ToUpperInvariant() };
|
||||
if (option.TryGetValue(out FLinearColor color, "Value"))
|
||||
|
|
@ -189,4 +189,4 @@ public class Options
|
|||
c.DrawText(Option, margin + _SPACE * 2, top + 20 * 1.1f, _optionPaint);
|
||||
top += _HEIGHT + _SPACE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,8 +107,11 @@ public class CreatorPackage : IDisposable
|
|||
case "FortChallengeBundleScheduleDefinition":
|
||||
case "FortWeaponMeleeDualWieldItemDefinition":
|
||||
case "FortDailyRewardScheduleTokenDefinition":
|
||||
case "FortCreativeWeaponRangedItemDefinition":
|
||||
case "FortCreativeRealEstatePlotItemDefinition":
|
||||
case "AthenaDanceItemDefinition_AdHocSquadsJoin_C":
|
||||
case "StWFortAccoladeItemDefinition":
|
||||
case "FortSmartBuildingItemDefinition":
|
||||
creator = _style switch
|
||||
{
|
||||
EIconStyle.Cataba => new BaseCommunity(_object, _style, "Cataba"),
|
||||
|
|
@ -148,6 +151,7 @@ public class CreatorPackage : IDisposable
|
|||
case "FortFeatItemDefinition":
|
||||
case "FortQuestItemDefinition":
|
||||
case "FortQuestItemDefinition_Athena":
|
||||
case "FortQuestItemDefinition_Campaign":
|
||||
case "AthenaDailyQuestDefinition":
|
||||
case "FortUrgentQuestItemDefinition":
|
||||
creator = new Bases.FN.BaseQuest(_object, _style);
|
||||
|
|
@ -162,6 +166,7 @@ public class CreatorPackage : IDisposable
|
|||
case "FortItemAccessTokenType":
|
||||
creator = new BaseItemAccessToken(_object, _style);
|
||||
return true;
|
||||
case "FortCreativeOption":
|
||||
case "PlaylistUserOptionEnum":
|
||||
case "PlaylistUserOptionBool":
|
||||
case "PlaylistUserOptionString":
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ public class Typefaces
|
|||
Description = OnTheFly(_SPELLBREAK_BASE_PATH + _MONTSERRAT_SEMIBOLD + _EXT);
|
||||
break;
|
||||
}
|
||||
case FGame.PandaGame:
|
||||
case FGame.MultiVersus:
|
||||
{
|
||||
DisplayName = OnTheFly(_PANDAGAME_BASE_PATH + language switch
|
||||
{
|
||||
|
|
|
|||
|
|
@ -146,9 +146,9 @@ public static class Utils
|
|||
return _applicationView.CUE4Parse.Provider.TryLoadObject(fullPath, out export);
|
||||
}
|
||||
|
||||
public static IEnumerable<UObject> LoadExports(string fullPath)
|
||||
public static IEnumerable<UObject> LoadExports(string packagePath)
|
||||
{
|
||||
return _applicationView.CUE4Parse.Provider.LoadObjectExports(fullPath);
|
||||
return _applicationView.CUE4Parse.Provider.LoadAllObjects(packagePath);
|
||||
}
|
||||
|
||||
public static float GetMaxFontSize(double sectorSize, SKTypeface typeface, string text, float degreeOfCertainty = 1f, float maxFont = 100f)
|
||||
|
|
|
|||
|
|
@ -92,8 +92,10 @@ public enum FGame
|
|||
Gameface,
|
||||
[Description("Sea of Thieves")]
|
||||
Athena,
|
||||
[Description("Your Beloved ™ Panda")]
|
||||
[Description("DEPRECATED")]
|
||||
PandaGame,
|
||||
[Description("MultiVersus")]
|
||||
MultiVersus,
|
||||
[Description("Tower of Fantasy")]
|
||||
Hotta,
|
||||
[Description("eFootball 2023")]
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ public static class AvalonExtensions
|
|||
private static readonly IHighlightingDefinition _xmlHighlighter = LoadHighlighter("Xml.xshd");
|
||||
private static readonly IHighlightingDefinition _cppHighlighter = LoadHighlighter("Cpp.xshd");
|
||||
private static readonly IHighlightingDefinition _changelogHighlighter = LoadHighlighter("Changelog.xshd");
|
||||
private static readonly IHighlightingDefinition _ulangHighlighter = LoadHighlighter("ULang.xshd");
|
||||
private static readonly IHighlightingDefinition _verseHighlighter = LoadHighlighter("Verse.xshd");
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static IHighlightingDefinition LoadHighlighter(string resourceName)
|
||||
|
|
@ -40,8 +40,8 @@ public static class AvalonExtensions
|
|||
return _cppHighlighter;
|
||||
case "changelog":
|
||||
return _changelogHighlighter;
|
||||
case "ulang":
|
||||
return _ulangHighlighter;
|
||||
case "verse":
|
||||
return _verseHighlighter;
|
||||
case "bat":
|
||||
case "txt":
|
||||
case "pem":
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace FModel.Extensions;
|
||||
|
||||
|
|
@ -112,6 +113,37 @@ public static class StringExtensions
|
|||
return 1;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int GetKismetLineNumber(this string s, string input)
|
||||
{
|
||||
var match = Regex.Match(input, @"^(.+)\[(\d+)\]$");
|
||||
var name = match.Groups[1].Value;
|
||||
int index = int.Parse(match.Groups[2].Value);
|
||||
var lineToFind = $" \"Name\": \"{name}\",";
|
||||
var offset = $"\"StatementIndex\": {index}";
|
||||
using var reader = new StringReader(s);
|
||||
var lineNum = 0;
|
||||
string line;
|
||||
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
{
|
||||
lineNum++;
|
||||
if (line.Equals(lineToFind, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var objectLine = lineNum;
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
{
|
||||
lineNum++;
|
||||
if (line.Contains(offset, StringComparison.OrdinalIgnoreCase))
|
||||
return lineNum;
|
||||
}
|
||||
return objectLine;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int GetLineNumber(this string s, int index)
|
||||
{
|
||||
|
|
@ -130,4 +162,4 @@ public static class StringExtensions
|
|||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@
|
|||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ApplicationIcon>FModel.ico</ApplicationIcon>
|
||||
<Version>4.4.1.1</Version>
|
||||
<AssemblyVersion>4.4.1.1</AssemblyVersion>
|
||||
<FileVersion>4.4.1.1</FileVersion>
|
||||
<Version>4.4.3.0</Version>
|
||||
<AssemblyVersion>4.4.3.0</AssemblyVersion>
|
||||
<FileVersion>4.4.3.0</FileVersion>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsPublishable>true</IsPublishable>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
|
|
@ -69,7 +69,7 @@
|
|||
<None Remove="Resources\athena.png" />
|
||||
<None Remove="Resources\Json.xshd" />
|
||||
<None Remove="Resources\Ini.xshd" />
|
||||
<None Remove="Resources\Ulang.xshd" />
|
||||
<None Remove="Resources\Verse.xshd" />
|
||||
<None Remove="Resources\Xml.xshd" />
|
||||
<None Remove="Resources\Cpp.xshd" />
|
||||
<None Remove="Resources\Changelog.xshd" />
|
||||
|
|
@ -112,7 +112,7 @@
|
|||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\Json.xshd" />
|
||||
<EmbeddedResource Include="Resources\Ini.xshd" />
|
||||
<EmbeddedResource Include="Resources\ULang.xshd" />
|
||||
<EmbeddedResource Include="Resources\Verse.xshd" />
|
||||
<EmbeddedResource Include="Resources\Xml.xshd" />
|
||||
<EmbeddedResource Include="Resources\Cpp.xshd" />
|
||||
<EmbeddedResource Include="Resources\Changelog.xshd" />
|
||||
|
|
@ -135,19 +135,19 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="AdonisUI" Version="1.17.1" />
|
||||
<PackageReference Include="AdonisUI.ClassicTheme" Version="1.17.1" />
|
||||
<PackageReference Include="Autoupdater.NET.Official" Version="1.7.3" />
|
||||
<PackageReference Include="AvalonEdit" Version="6.1.3.50" />
|
||||
<PackageReference Include="Autoupdater.NET.Official" Version="1.7.6" />
|
||||
<PackageReference Include="AvalonEdit" Version="6.2.0.78" />
|
||||
<PackageReference Include="CSCore" Version="1.2.1.2" />
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.0.175" />
|
||||
<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="DiscordRichPresence" Version="1.1.3.18" />
|
||||
<PackageReference Include="ImGui.NET" Version="1.89.4" />
|
||||
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.3.5" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
<PackageReference Include="NVorbis" Version="0.10.5" />
|
||||
<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="OpenTK" Version="4.7.7" />
|
||||
<PackageReference Include="RestSharp" Version="108.0.3" />
|
||||
<PackageReference Include="Serilog" Version="2.12.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" />
|
||||
|
|
@ -227,6 +227,15 @@
|
|||
<Resource Include="Resources\pz.png" />
|
||||
<Resource Include="Resources\pointlight.png" />
|
||||
<Resource Include="Resources\spotlight.png" />
|
||||
<Resource Include="Resources\link_on.png" />
|
||||
<Resource Include="Resources\link_off.png" />
|
||||
<Resource Include="Resources\link_has.png" />
|
||||
<Resource Include="Resources\tl_play.png" />
|
||||
<Resource Include="Resources\tl_pause.png" />
|
||||
<Resource Include="Resources\tl_rewind.png" />
|
||||
<Resource Include="Resources\tl_forward.png" />
|
||||
<Resource Include="Resources\tl_previous.png" />
|
||||
<Resource Include="Resources\tl_next.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -3,11 +3,14 @@ using System.Collections.Generic;
|
|||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using ImGuiNET;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using OpenTK.Windowing.Desktop;
|
||||
using OpenTK.Windowing.GraphicsLibraryFramework;
|
||||
using ErrorCode = OpenTK.Graphics.OpenGL4.ErrorCode;
|
||||
using Keys = OpenTK.Windowing.GraphicsLibraryFramework.Keys;
|
||||
|
||||
namespace FModel.Framework;
|
||||
|
||||
|
|
@ -33,11 +36,12 @@ public class ImGuiController : IDisposable
|
|||
private int _windowHeight;
|
||||
// private string _iniPath;
|
||||
|
||||
private ImFontPtr _normal;
|
||||
private ImFontPtr _bold;
|
||||
private ImFontPtr _semiBold;
|
||||
public ImFontPtr FontNormal;
|
||||
public ImFontPtr FontBold;
|
||||
public ImFontPtr FontSemiBold;
|
||||
|
||||
private readonly Vector2 _scaleFactor = Vector2.One;
|
||||
public readonly float DpiScale = GetDpiScale();
|
||||
|
||||
private static bool KHRDebugAvailable = false;
|
||||
|
||||
|
|
@ -57,9 +61,9 @@ public class ImGuiController : IDisposable
|
|||
// 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);
|
||||
FontNormal = io.Fonts.AddFontFromFileTTF("C:\\Windows\\Fonts\\segoeui.ttf", 16 * DpiScale);
|
||||
FontBold = io.Fonts.AddFontFromFileTTF("C:\\Windows\\Fonts\\segoeuib.ttf", 16 * DpiScale);
|
||||
FontSemiBold = io.Fonts.AddFontFromFileTTF("C:\\Windows\\Fonts\\seguisb.ttf", 16 * DpiScale);
|
||||
|
||||
io.BackendFlags |= ImGuiBackendFlags.RendererHasVtxOffset;
|
||||
io.ConfigFlags |= ImGuiConfigFlags.NavEnableKeyboard;
|
||||
|
|
@ -75,13 +79,13 @@ public class ImGuiController : IDisposable
|
|||
_frameBegun = true;
|
||||
}
|
||||
|
||||
public void Bold() => PushFont(_bold);
|
||||
public void SemiBold() => PushFont(_semiBold);
|
||||
public void Bold() => PushFont(FontBold);
|
||||
public void SemiBold() => PushFont(FontSemiBold);
|
||||
|
||||
public void PopFont()
|
||||
{
|
||||
ImGui.PopFont();
|
||||
PushFont(_normal);
|
||||
PushFont(FontNormal);
|
||||
}
|
||||
|
||||
private void PushFont(ImFontPtr ptr) => ImGui.PushFont(ptr);
|
||||
|
|
@ -634,4 +638,9 @@ outputColor = color * texture(in_fontTexture, texCoord);
|
|||
Debug.Print($"{title} ({i++}): {error}");
|
||||
}
|
||||
}
|
||||
|
||||
public static float GetDpiScale()
|
||||
{
|
||||
return Math.Max((float)(Screen.PrimaryScreen.Bounds.Width / SystemParameters.PrimaryScreenWidth), (float)(Screen.PrimaryScreen.Bounds.Height / SystemParameters.PrimaryScreenHeight));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,11 +16,6 @@ public static class Helper
|
|||
internal readonly ulong UlongValue;
|
||||
}
|
||||
|
||||
public static bool IAmThePanda(string key)
|
||||
=> key.Length == 11 &&
|
||||
key.StartsWith("mu", StringComparison.OrdinalIgnoreCase) &&
|
||||
key.EndsWith("sus", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public static string FixKey(string key)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key))
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
xmlns:local="clr-namespace:FModel"
|
||||
xmlns:controls="clr-namespace:FModel.Views.Resources.Controls"
|
||||
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
|
||||
xmlns:settings="clr-namespace:FModel.Settings"
|
||||
xmlns:services="clr-namespace:FModel.Services"
|
||||
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
|
||||
xmlns:adonisControls="clr-namespace:AdonisUI.Controls;assembly=AdonisUI"
|
||||
xmlns:adonisExtensions="clr-namespace:AdonisUI.Extensions;assembly=AdonisUI"
|
||||
|
|
@ -12,10 +14,18 @@
|
|||
Width="{Binding Source={x:Static SystemParameters.MaximizedPrimaryScreenWidth}, Converter={converters:RatioConverter}, ConverterParameter='0.75'}">
|
||||
<adonisControls:AdonisWindow.Style>
|
||||
<Style TargetType="adonisControls:AdonisWindow" BasedOn="{StaticResource {x:Type adonisControls:AdonisWindow}}" >
|
||||
<Setter Property="Title" Value="FModel" />
|
||||
<Setter Property="Title" Value="{Binding DataContext.InitialWindowTitle, RelativeSource={RelativeSource Self}}" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding DataContext.TitleExtra, RelativeSource={RelativeSource Self}, Converter={x:Static converters:IsNullToBoolReversedConverter.Instance}}" Value="True">
|
||||
<Setter Property="Title" Value="{Binding DataContext.TitleExtra, RelativeSource={RelativeSource Self}, StringFormat={}FModel {0}}" />
|
||||
<Setter Property="Title">
|
||||
<Setter.Value>
|
||||
<MultiBinding StringFormat="{}{0} - {1} {2}">
|
||||
<Binding Path="DataContext.InitialWindowTitle" RelativeSource="{RelativeSource Self}" />
|
||||
<Binding Path="DataContext.GameDisplayName" RelativeSource="{RelativeSource Self}" />
|
||||
<Binding Path="DataContext.TitleExtra" RelativeSource="{RelativeSource Self}" />
|
||||
</MultiBinding>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
|
@ -95,7 +105,7 @@
|
|||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Header="Auto Open Sounds" IsCheckable="True" StaysOpenOnClick="True"
|
||||
IsChecked="{Binding IsAutoOpenSounds, Source={x:Static local:Settings.UserSettings.Default}}" />
|
||||
IsChecked="{Binding IsAutoOpenSounds, Source={x:Static settings:UserSettings.Default}}" />
|
||||
</MenuItem>
|
||||
<MenuItem Header="Views">
|
||||
<MenuItem Header="3D Viewer" Command="{Binding MenuCommand}" CommandParameter="Views_3dViewer">
|
||||
|
|
@ -221,7 +231,7 @@
|
|||
|
||||
<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 Status.IsReady}"
|
||||
SelectedItem="{Binding LoadingMode, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}">
|
||||
SelectedItem="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}, Mode=TwoWay}">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={x:Static converters:EnumToStringConverter.Instance}}" />
|
||||
|
|
@ -308,15 +318,15 @@
|
|||
</Canvas>
|
||||
</Viewbox>
|
||||
</Button>
|
||||
<Button Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarButton}}" ToolTip="Expand All (not appropriate for huge amount of folders)" Padding="4"
|
||||
Command="{Binding MenuCommand}" CommandParameter="ToolBox_Expand_All">
|
||||
<Viewbox Width="16" Height="16" HorizontalAlignment="Center">
|
||||
<Canvas Width="24" Height="24">
|
||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource UnfoldIcon}" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</Button>
|
||||
<Button Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarButton}}" ToolTip="Collapse All (not appropriate for huge amount of folders)" Padding="4"
|
||||
<!-- <Button Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarButton}}" ToolTip="Expand All (not appropriate for huge amount of folders)" Padding="4" -->
|
||||
<!-- Command="{Binding MenuCommand}" CommandParameter="ToolBox_Expand_All"> -->
|
||||
<!-- <Viewbox Width="16" Height="16" HorizontalAlignment="Center"> -->
|
||||
<!-- <Canvas Width="24" Height="24"> -->
|
||||
<!-- <Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource UnfoldIcon}" /> -->
|
||||
<!-- </Canvas> -->
|
||||
<!-- </Viewbox> -->
|
||||
<!-- </Button> -->
|
||||
<Button Style="{DynamicResource {x:Static adonisUi:Styles.ToolbarButton}}" ToolTip="Collapse All" Padding="4"
|
||||
Command="{Binding MenuCommand}" CommandParameter="ToolBox_Collapse_All">
|
||||
<Viewbox Width="16" Height="16" HorizontalAlignment="Center">
|
||||
<Canvas Width="24" Height="24">
|
||||
|
|
@ -339,6 +349,7 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Header="Export Folder's Packages Raw Data (.uasset)" Click="OnFolderExportClick">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
|
|
@ -494,6 +505,7 @@
|
|||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Header="Export Raw Data (.uasset)" Command="{Binding DataContext.RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
|
|
@ -669,7 +681,7 @@
|
|||
|
||||
<TabControl Grid.Row="0" x:Name="TabControlName" Style="{StaticResource GameFilesTabControl}" />
|
||||
<Expander Grid.Row="1" Margin="0 5 0 5" ExpandDirection="Down"
|
||||
IsExpanded="{Binding IsLoggerExpanded, Source={x:Static local:Settings.UserSettings.Default}}">
|
||||
IsExpanded="{Binding IsLoggerExpanded, Source={x:Static settings:UserSettings.Default}}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
|
|
@ -732,7 +744,7 @@
|
|||
<Setter Property="Background" Value="{DynamicResource {x:Static adonisUi:Brushes.ErrorBrush}}" />
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding StatusChangeAttempted, Source={x:Static local:Services.ApplicationService.ThreadWorkerView}}" Value="True">
|
||||
<DataTrigger Binding="{Binding StatusChangeAttempted, Source={x:Static services:ApplicationService.ThreadWorkerView}}" Value="True">
|
||||
<DataTrigger.EnterActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard Duration="0:0:0.8">
|
||||
|
|
@ -749,7 +761,7 @@
|
|||
</BeginStoryboard>
|
||||
</DataTrigger.EnterActions>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding OperationCancelled, Source={x:Static local:Services.ApplicationService.ThreadWorkerView}}" Value="True">
|
||||
<DataTrigger Binding="{Binding OperationCancelled, Source={x:Static services:ApplicationService.ThreadWorkerView}}" Value="True">
|
||||
<DataTrigger.EnterActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard Duration="0:0:1">
|
||||
|
|
@ -778,7 +790,7 @@
|
|||
</DataTrigger>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding CanBeCanceled, Source={x:Static local:Services.ApplicationService.ThreadWorkerView}}" Value="True" />
|
||||
<Condition Binding="{Binding CanBeCanceled, Source={x:Static services:ApplicationService.ThreadWorkerView}}" Value="True" />
|
||||
<Condition Binding="{Binding Status.Kind}" Value="{x:Static local:EStatusKind.Loading}" />
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="Text" Value="{Binding Status.Label, StringFormat='{}{0} … ESC to Cancel'}" />
|
||||
|
|
@ -803,7 +815,7 @@
|
|||
<StatusBarItem.Style>
|
||||
<Style TargetType="StatusBarItem">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsAutoOpenSounds, Source={x:Static local:Settings.UserSettings.Default}}" Value="False">
|
||||
<DataTrigger Binding="{Binding IsAutoOpenSounds, Source={x:Static settings:UserSettings.Default}}" Value="False">
|
||||
<Setter Property="Visibility" Value="Hidden" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
|
@ -45,6 +46,7 @@ public partial class MainWindow
|
|||
|
||||
private async void OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var newOrUpdated = UserSettings.Default.ShowChangelog;
|
||||
#if !DEBUG
|
||||
ApplicationService.ApiEndpointView.FModelApi.CheckForUpdates(UserSettings.Default.UpdateMode);
|
||||
#endif
|
||||
|
|
@ -62,28 +64,29 @@ public partial class MainWindow
|
|||
|
||||
await _applicationView.CUE4Parse.Initialize();
|
||||
await _applicationView.AesManager.InitAes();
|
||||
await _applicationView.AesManager.UpdateProvider(true);
|
||||
await _applicationView.UpdateProvider(true);
|
||||
#if !DEBUG
|
||||
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);
|
||||
await Task.WhenAll(
|
||||
_applicationView.CUE4Parse.VerifyConsoleVariables(),
|
||||
_applicationView.CUE4Parse.VerifyVirtualCache(),
|
||||
_applicationView.CUE4Parse.VerifyContentBuildManifest(),
|
||||
_applicationView.CUE4Parse.InitMappings(),
|
||||
_applicationView.InitImGuiSettings(newOrUpdated),
|
||||
_applicationView.InitVgmStream(),
|
||||
_applicationView.InitOodle(),
|
||||
Task.Run(() =>
|
||||
{
|
||||
if (UserSettings.Default.DiscordRpc == EDiscordRpc.Always)
|
||||
_discordHandler.Initialize(_applicationView.GameDisplayName);
|
||||
})
|
||||
).ConfigureAwait(false);
|
||||
|
||||
#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"));
|
||||
// "fortnitegame/Content/Characters/Player/Female/Medium/Bodies/F_MED_Ballerina/Meshes/F_MED_Ballerina.uasset"));
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
@ -175,6 +178,11 @@ public partial class MainWindow
|
|||
if (AssetsFolderName.SelectedItem is TreeItem folder)
|
||||
{
|
||||
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.ExportFolder(cancellationToken, folder); });
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
{
|
||||
FLogger.Text("Successfully exported ", Constants.WHITE);
|
||||
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.RawDataDirectory, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -183,6 +191,11 @@ public partial class MainWindow
|
|||
if (AssetsFolderName.SelectedItem is TreeItem folder)
|
||||
{
|
||||
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.SaveFolder(cancellationToken, folder); });
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
{
|
||||
FLogger.Text("Successfully saved ", Constants.WHITE);
|
||||
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.PropertiesDirectory, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -191,6 +204,11 @@ public partial class MainWindow
|
|||
if (AssetsFolderName.SelectedItem is TreeItem folder)
|
||||
{
|
||||
await _threadWorkerView.Begin(cancellationToken => { _applicationView.CUE4Parse.TextureFolder(cancellationToken, folder); });
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
{
|
||||
FLogger.Text("Successfully saved ", Constants.WHITE);
|
||||
FLogger.Link(folder.PathAtThisPoint, UserSettings.Default.TextureDirectory, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -215,8 +233,8 @@ public partial class MainWindow
|
|||
if (AssetsFolderName.SelectedItem is not TreeItem folder) return;
|
||||
|
||||
_applicationView.CustomDirectories.Add(new CustomDirectory(folder.Header, folder.PathAtThisPoint));
|
||||
FLogger.AppendInformation();
|
||||
FLogger.AppendText($"Successfully saved '{folder.PathAtThisPoint}' as a new custom directory", Constants.WHITE, true);
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
FLogger.Text($"Successfully saved '{folder.PathAtThisPoint}' as a new custom directory", Constants.WHITE, true));
|
||||
}
|
||||
|
||||
private void OnCopyDirectoryPathClick(object sender, RoutedEventArgs e)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<SyntaxDefinition name="Ulang Visual Process Language" xmlns="http://icsharpcode.net/sharpdevelop/syntaxdefinition/2008"
|
||||
extensions=".ulang">
|
||||
<SyntaxDefinition name="Verse Visual Process Language" xmlns="http://icsharpcode.net/sharpdevelop/syntaxdefinition/2008"
|
||||
extensions=".verse">
|
||||
|
||||
<Color name="Constant" foreground="#C792DD" />
|
||||
<Color name="Method" foreground="#FFCB6B" />
|
||||
|
|
@ -43,10 +43,9 @@ struct Parameters
|
|||
bool HasAo;
|
||||
|
||||
vec4 EmissiveRegion;
|
||||
float Specular;
|
||||
float Roughness;
|
||||
float RoughnessMin;
|
||||
float RoughnessMax;
|
||||
float EmissiveMult;
|
||||
float UVScale;
|
||||
};
|
||||
|
||||
struct BaseLight
|
||||
|
|
@ -91,6 +90,7 @@ uniform Light uLights[MAX_LIGHT_COUNT];
|
|||
uniform int uNumLights;
|
||||
uniform int uUvCount;
|
||||
uniform bool uHasVertexColors;
|
||||
uniform vec3 uSectionColor;
|
||||
uniform bool bVertexColors[6];
|
||||
uniform vec3 uViewPos;
|
||||
|
||||
|
|
@ -101,11 +101,6 @@ int LayerToIndex()
|
|||
return clamp(int(fTexLayer), 0, uUvCount - 1);
|
||||
}
|
||||
|
||||
vec2 ScaledTexCoords()
|
||||
{
|
||||
return fTexCoords * uParameters.UVScale;
|
||||
}
|
||||
|
||||
vec4 SamplerToVector(sampler2D s, vec2 coords)
|
||||
{
|
||||
return texture(s, coords);
|
||||
|
|
@ -113,7 +108,7 @@ vec4 SamplerToVector(sampler2D s, vec2 coords)
|
|||
|
||||
vec4 SamplerToVector(sampler2D s)
|
||||
{
|
||||
return SamplerToVector(s, ScaledTexCoords());
|
||||
return SamplerToVector(s, fTexCoords);
|
||||
}
|
||||
|
||||
vec3 ComputeNormals(int layer)
|
||||
|
|
@ -153,7 +148,7 @@ vec3 CalcLight(int layer, vec3 normals, vec3 position, vec3 color, float attenua
|
|||
{
|
||||
vec3 fLambert = SamplerToVector(uParameters.Diffuse[layer].Sampler).rgb * uParameters.Diffuse[layer].Color.rgb;
|
||||
vec3 specular_masks = SamplerToVector(uParameters.SpecularMasks[layer].Sampler).rgb;
|
||||
float roughness = max(0.0f, specular_masks.b * uParameters.Roughness);
|
||||
float roughness = mix(uParameters.RoughnessMin, uParameters.RoughnessMax, specular_masks.b);
|
||||
|
||||
vec3 l = normalize(uViewPos - fPos);
|
||||
|
||||
|
|
@ -174,11 +169,11 @@ vec3 CalcLight(int layer, vec3 normals, vec3 position, vec3 color, float attenua
|
|||
|
||||
vec3 specBrdfNom = ggxDistribution(roughness, nDotH) * geomSmith(roughness, nDotL) * geomSmith(roughness, nDotV) * f;
|
||||
float specBrdfDenom = 4.0 * nDotV * nDotL + 0.0001;
|
||||
vec3 specBrdf = uParameters.Specular * specular_masks.r * specBrdfNom / specBrdfDenom;
|
||||
vec3 specBrdf = specBrdfNom / specBrdfDenom;
|
||||
|
||||
vec3 diffuseBrdf = fLambert;
|
||||
if (!global) diffuseBrdf = kD * fLambert / PI;
|
||||
return (diffuseBrdf + specBrdf) * color * nDotL * attenuation;
|
||||
return (diffuseBrdf + specBrdf) * color * attenuation * nDotL;
|
||||
}
|
||||
|
||||
vec3 CalcBaseLight(int layer, vec3 normals, BaseLight base, float attenuation, bool global)
|
||||
|
|
@ -217,21 +212,23 @@ vec3 CalcSpotLight(int layer, vec3 normals, Light light)
|
|||
|
||||
void main()
|
||||
{
|
||||
if (bVertexColors[2] && uHasVertexColors)
|
||||
if (bVertexColors[1])
|
||||
{
|
||||
FragColor = vec4(uSectionColor, 1.0);
|
||||
}
|
||||
else if (bVertexColors[2] && uHasVertexColors)
|
||||
{
|
||||
FragColor = fColor;
|
||||
}
|
||||
else if (bVertexColors[3])
|
||||
{
|
||||
FragColor = vec4(fNormal, 1);
|
||||
int layer = LayerToIndex();
|
||||
vec3 normals = ComputeNormals(layer);
|
||||
FragColor = vec4(normals, 1.0);
|
||||
}
|
||||
else if (bVertexColors[4])
|
||||
{
|
||||
FragColor = vec4(fTangent, 1);
|
||||
}
|
||||
else if (bVertexColors[5])
|
||||
{
|
||||
FragColor = vec4(fTexCoords, 0, 1);
|
||||
FragColor = vec4(fTexCoords, 0.0, 1.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -248,10 +245,10 @@ void main()
|
|||
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);
|
||||
result = vec3(uParameters.Ao.AmbientOcclusion) * result * m.r;
|
||||
}
|
||||
|
||||
vec2 coords = ScaledTexCoords();
|
||||
vec2 coords = fTexCoords;
|
||||
if (coords.x > uParameters.EmissiveRegion.x &&
|
||||
coords.y > uParameters.EmissiveRegion.y &&
|
||||
coords.x < uParameters.EmissiveRegion.z &&
|
||||
|
|
@ -265,7 +262,6 @@ void main()
|
|||
result += uParameters.Emissive[layer].Color.rgb * emissive.rgb * uParameters.EmissiveMult;
|
||||
}
|
||||
|
||||
if (!bVertexColors[1])
|
||||
{
|
||||
result += CalcLight(layer, normals, uViewPos, vec3(0.75), 1.0, false);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#version 460 core
|
||||
#version 460 core
|
||||
|
||||
layout (location = 1) in vec3 vPos;
|
||||
layout (location = 2) in vec3 vNormal;
|
||||
|
|
@ -9,15 +9,17 @@ 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;
|
||||
layout (location = 13) in vec3 vMorphTargetPos;
|
||||
layout (location = 14) in vec3 vMorphTargetTangent;
|
||||
|
||||
//const int MAX_BONES = 0;
|
||||
//const int MAX_BONE_INFLUENCE = 0;
|
||||
layout(std430, binding = 1) buffer BoneMatrices
|
||||
{
|
||||
mat4 uFinalBonesMatrix[];
|
||||
};
|
||||
|
||||
uniform mat4 uView;
|
||||
uniform mat4 uProjection;
|
||||
uniform float uMorphTime;
|
||||
//uniform mat4 uFinalBonesMatrix[MAX_BONES];
|
||||
|
||||
out vec3 fPos;
|
||||
out vec3 fNormal;
|
||||
|
|
@ -28,24 +30,41 @@ 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];
|
||||
// }
|
||||
vec4 bindPos = vec4(mix(vPos, vMorphTargetPos, uMorphTime), 1.0);
|
||||
vec4 bindNormal = vec4(vNormal, 1.0);
|
||||
vec4 bindTangent = vec4(mix(vTangent, vMorphTargetTangent, uMorphTime), 1.0);
|
||||
|
||||
gl_Position = uProjection * uView * vInstanceMatrix * pos;
|
||||
vec4 finalPos = vec4(0.0);
|
||||
vec4 finalNormal = vec4(0.0);
|
||||
vec4 finalTangent = vec4(0.0);
|
||||
if (vBoneIds != vBoneWeights)
|
||||
{
|
||||
for(int i = 0 ; i < 4; i++)
|
||||
{
|
||||
int boneIndex = int(vBoneIds[i]);
|
||||
if(boneIndex < 0) break;
|
||||
|
||||
fPos = vec3(vInstanceMatrix * pos);
|
||||
fNormal = mat3(transpose(inverse(vInstanceMatrix))) * vNormal;
|
||||
fTangent = mat3(transpose(inverse(vInstanceMatrix))) * vTangent;
|
||||
mat4 boneMatrix = uFinalBonesMatrix[boneIndex];
|
||||
mat4 inverseBoneMatrix = transpose(inverse(boneMatrix));
|
||||
float weight = vBoneWeights[i];
|
||||
|
||||
finalPos += boneMatrix * bindPos * weight;
|
||||
finalNormal += inverseBoneMatrix * bindNormal * weight;
|
||||
finalTangent += inverseBoneMatrix * bindTangent * weight;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
finalPos = bindPos;
|
||||
finalNormal = bindNormal;
|
||||
finalTangent = bindTangent;
|
||||
}
|
||||
|
||||
gl_Position = uProjection * uView * vInstanceMatrix * finalPos;
|
||||
|
||||
fPos = vec3(vInstanceMatrix * finalPos);
|
||||
fNormal = vec3(transpose(inverse(vInstanceMatrix)) * finalNormal);
|
||||
fTangent = vec3(transpose(inverse(vInstanceMatrix)) * finalTangent);
|
||||
fTexCoords = vTexCoords;
|
||||
fTexLayer = vTexLayer;
|
||||
fColor = vColor;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,13 @@ out vec2 fTexCoords;
|
|||
|
||||
void main()
|
||||
{
|
||||
gl_Position = uProjection * uView * vInstanceMatrix * vec4(inverse(mat3(uView)) * vPos, 1.0);
|
||||
fTexCoords = -vPos.xy;
|
||||
float scale = 0.075;
|
||||
mat4 result;
|
||||
result[0] = vec4(scale, 0.0, 0.0, 0.0);
|
||||
result[1] = vec4(0.0, scale, 0.0, 0.0);
|
||||
result[2] = vec4(0.0, 0.0, scale, 0.0);
|
||||
result[3] = vInstanceMatrix[3];
|
||||
|
||||
gl_Position = uProjection * uView * result * vec4(inverse(mat3(uView)) * vPos, 1.0);
|
||||
fTexCoords = -vPos.xy * 0.5 + 0.5; // fits the whole rectangle
|
||||
}
|
||||
|
|
|
|||
BIN
FModel/Resources/link_has.png
Normal file
|
After Width: | Height: | Size: 322 B |
BIN
FModel/Resources/link_off.png
Normal file
|
After Width: | Height: | Size: 311 B |
BIN
FModel/Resources/link_on.png
Normal file
|
After Width: | Height: | Size: 235 B |
|
|
@ -2,8 +2,15 @@
|
|||
|
||||
layout (location = 1) in vec3 vPos;
|
||||
layout (location = 2) in vec3 vNormal;
|
||||
layout (location = 7) in vec4 vBoneIds;
|
||||
layout (location = 8) in vec4 vBoneWeights;
|
||||
layout (location = 9) in mat4 vInstanceMatrix;
|
||||
layout (location = 13) in vec3 vMorphTarget;
|
||||
layout (location = 13) in vec3 vMorphTargetPos;
|
||||
|
||||
layout(std430, binding = 1) buffer BoneMatrices
|
||||
{
|
||||
mat4 uFinalBonesMatrix[];
|
||||
};
|
||||
|
||||
uniform mat4 uView;
|
||||
uniform vec3 uViewPos;
|
||||
|
|
@ -12,10 +19,35 @@ uniform float uMorphTime;
|
|||
|
||||
void main()
|
||||
{
|
||||
vec3 pos = vec3(vInstanceMatrix * vec4(mix(vPos, vMorphTarget, uMorphTime), 1.0));
|
||||
vec3 nor = mat3(transpose(inverse(vInstanceMatrix))) * vNormal;
|
||||
vec4 bindPos = vec4(mix(vPos, vMorphTargetPos, uMorphTime), 1.0);
|
||||
vec4 bindNormal = vec4(vNormal, 1.0);
|
||||
|
||||
float scaleFactor = distance(pos, uViewPos) * 0.0025;
|
||||
vec3 scaleVertex = pos + nor * scaleFactor;
|
||||
gl_Position = uProjection * uView * vec4(scaleVertex, 1.0);
|
||||
vec4 finalPos = vec4(0.0);
|
||||
vec4 finalNormal = vec4(0.0);
|
||||
if (vBoneIds != vBoneWeights)
|
||||
{
|
||||
for(int i = 0 ; i < 4; i++)
|
||||
{
|
||||
int boneIndex = int(vBoneIds[i]);
|
||||
if(boneIndex < 0) break;
|
||||
|
||||
mat4 boneMatrix = uFinalBonesMatrix[boneIndex];
|
||||
float weight = vBoneWeights[i];
|
||||
|
||||
finalPos += boneMatrix * bindPos * weight;
|
||||
finalNormal += transpose(inverse(boneMatrix)) * bindNormal * weight;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
finalPos = bindPos;
|
||||
finalNormal = bindNormal;
|
||||
}
|
||||
|
||||
finalPos = vInstanceMatrix * finalPos;
|
||||
float scaleFactor = distance(vec3(finalPos), uViewPos) * 0.0035;
|
||||
vec4 nor = transpose(inverse(vInstanceMatrix)) * normalize(finalNormal) * scaleFactor;
|
||||
finalPos.xyz += nor.xyz;
|
||||
|
||||
gl_Position = uProjection * uView * finalPos;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,15 @@
|
|||
#version 460 core
|
||||
|
||||
layout (location = 1) in vec3 vPos;
|
||||
layout (location = 7) in vec4 vBoneIds;
|
||||
layout (location = 8) in vec4 vBoneWeights;
|
||||
layout (location = 9) in mat4 vInstanceMatrix;
|
||||
layout (location = 13) in vec3 vMorphTarget;
|
||||
layout (location = 13) in vec3 vMorphTargetPos;
|
||||
|
||||
layout(std430, binding = 1) buffer BoneMatrices
|
||||
{
|
||||
mat4 uFinalBonesMatrix[];
|
||||
};
|
||||
|
||||
uniform mat4 uView;
|
||||
uniform mat4 uProjection;
|
||||
|
|
@ -10,6 +17,20 @@ uniform float uMorphTime;
|
|||
|
||||
void main()
|
||||
{
|
||||
vec3 pos = mix(vPos, vMorphTarget, uMorphTime);
|
||||
gl_Position = uProjection * uView * vInstanceMatrix * vec4(pos, 1.0);
|
||||
vec4 bindPos = vec4(mix(vPos, vMorphTargetPos, uMorphTime), 1.0);
|
||||
|
||||
vec4 finalPos = vec4(0.0);
|
||||
if (vBoneIds != vBoneWeights)
|
||||
{
|
||||
for(int i = 0 ; i < 4; i++)
|
||||
{
|
||||
int boneIndex = int(vBoneIds[i]);
|
||||
if(boneIndex < 0) break;
|
||||
|
||||
finalPos += uFinalBonesMatrix[boneIndex] * bindPos * vBoneWeights[i];
|
||||
}
|
||||
}
|
||||
else finalPos = bindPos;
|
||||
|
||||
gl_Position = uProjection * uView * vInstanceMatrix * finalPos;
|
||||
}
|
||||
|
|
|
|||
BIN
FModel/Resources/tl_forward.png
Normal file
|
After Width: | Height: | Size: 210 B |
BIN
FModel/Resources/tl_next.png
Normal file
|
After Width: | Height: | Size: 221 B |
BIN
FModel/Resources/tl_pause.png
Normal file
|
After Width: | Height: | Size: 153 B |
BIN
FModel/Resources/tl_play.png
Normal file
|
After Width: | Height: | Size: 183 B |
BIN
FModel/Resources/tl_previous.png
Normal file
|
After Width: | Height: | Size: 219 B |
BIN
FModel/Resources/tl_rewind.png
Normal file
|
After Width: | Height: | Size: 222 B |
|
|
@ -31,14 +31,14 @@ namespace FModel.Services
|
|||
new() {Label = "Support us", Url = Constants.DONATE_LINK}
|
||||
};
|
||||
|
||||
public void Initialize(FGame game)
|
||||
public void Initialize(string gameName)
|
||||
{
|
||||
_currentPresence = new RichPresence
|
||||
{
|
||||
Assets = _staticAssets,
|
||||
Timestamps = _timestamps,
|
||||
Buttons = _buttons,
|
||||
Details = $"{game.GetDescription()} - Idling"
|
||||
Details = $"{gameName} - Idling"
|
||||
};
|
||||
|
||||
_client.OnReady += (_, args) => Log.Information("{Username}#{Discriminator} ({UserId}) is now ready", args.User.Username, args.User.Discriminator, args.User.ID);
|
||||
|
|
@ -48,7 +48,7 @@ namespace FModel.Services
|
|||
|
||||
public void UpdatePresence(CUE4ParseViewModel viewModel) =>
|
||||
UpdatePresence(
|
||||
$"{viewModel.Game.GetDescription()} - {viewModel.Provider.MountedVfs.Count}/{viewModel.Provider.MountedVfs.Count + viewModel.Provider.UnloadedVfs.Count} Packages",
|
||||
$"{viewModel.Provider.GameDisplayName ?? viewModel.Provider.GameName} - {viewModel.Provider.MountedVfs.Count}/{viewModel.Provider.MountedVfs.Count + viewModel.Provider.UnloadedVfs.Count} Packages",
|
||||
$"Mode: {UserSettings.Default.LoadingMode.GetDescription()} - {viewModel.SearchVm.ResultsCount:### ### ###} Loaded Assets".Trim());
|
||||
|
||||
public void UpdatePresence(string details, string state)
|
||||
|
|
@ -93,4 +93,4 @@ namespace FModel.Services
|
|||
_client.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ using CUE4Parse.UE4.Assets.Exports.Material;
|
|||
using FModel.Framework;
|
||||
using FModel.ViewModels;
|
||||
using FModel.ViewModels.ApiEndpoints.Models;
|
||||
using FModel.Views.Snooper;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace FModel.Settings
|
||||
|
|
@ -225,6 +226,13 @@ namespace FModel.Settings
|
|||
set => SetProperty(ref _imageMergerMargin, value);
|
||||
}
|
||||
|
||||
private bool _readScriptData;
|
||||
public bool ReadScriptData
|
||||
{
|
||||
get => _readScriptData;
|
||||
set => SetProperty(ref _readScriptData, value);
|
||||
}
|
||||
|
||||
// <gameDirectory as string, settings>
|
||||
// can't refactor to use this data layout for everything
|
||||
// because it will wipe old user settings that relies on FGame
|
||||
|
|
@ -263,7 +271,7 @@ namespace FModel.Settings
|
|||
{FGame.PortalWars, Constants._NO_PRESET_TRIGGER},
|
||||
{FGame.Gameface, Constants._NO_PRESET_TRIGGER},
|
||||
{FGame.Athena, Constants._NO_PRESET_TRIGGER},
|
||||
{FGame.PandaGame, Constants._NO_PRESET_TRIGGER},
|
||||
{FGame.MultiVersus, Constants._NO_PRESET_TRIGGER},
|
||||
{FGame.Hotta, Constants._NO_PRESET_TRIGGER},
|
||||
{FGame.eFootball, Constants._NO_PRESET_TRIGGER}
|
||||
};
|
||||
|
|
@ -294,7 +302,7 @@ namespace FModel.Settings
|
|||
{FGame.PortalWars, EGame.GAME_UE4_27},
|
||||
{FGame.Gameface, EGame.GAME_GTATheTrilogyDefinitiveEdition},
|
||||
{FGame.Athena, EGame.GAME_SeaOfThieves},
|
||||
{FGame.PandaGame, EGame.GAME_UE4_26},
|
||||
{FGame.MultiVersus, EGame.GAME_UE4_26},
|
||||
{FGame.Hotta, EGame.GAME_TowerOfFantasy},
|
||||
{FGame.eFootball, EGame.GAME_UE4_26}
|
||||
};
|
||||
|
|
@ -325,7 +333,7 @@ namespace FModel.Settings
|
|||
{FGame.PortalWars, null},
|
||||
{FGame.Gameface, null},
|
||||
{FGame.Athena, null},
|
||||
{FGame.PandaGame, null},
|
||||
{FGame.MultiVersus, null},
|
||||
{FGame.Hotta, null},
|
||||
{FGame.eFootball, null}
|
||||
};
|
||||
|
|
@ -356,7 +364,33 @@ namespace FModel.Settings
|
|||
{FGame.PortalWars, null},
|
||||
{FGame.Gameface, null},
|
||||
{FGame.Athena, null},
|
||||
{FGame.PandaGame, null},
|
||||
{FGame.MultiVersus, null},
|
||||
{FGame.Hotta, null},
|
||||
{FGame.eFootball, null}
|
||||
};
|
||||
|
||||
private IDictionary<FGame, Dictionary<string, KeyValuePair<string, string>>> _overridedMapStructTypes = new Dictionary<FGame, Dictionary<string, KeyValuePair<string, string>>>
|
||||
{
|
||||
{FGame.Unknown, null},
|
||||
{FGame.FortniteGame, null},
|
||||
{FGame.ShooterGame, null},
|
||||
{FGame.DeadByDaylight, null},
|
||||
{FGame.OakGame, null},
|
||||
{FGame.Dungeons, null},
|
||||
{FGame.WorldExplorers, null},
|
||||
{FGame.g3, null},
|
||||
{FGame.StateOfDecay2, null},
|
||||
{FGame.Prospect, null},
|
||||
{FGame.Indiana, null},
|
||||
{FGame.RogueCompany, null},
|
||||
{FGame.SwGame, null},
|
||||
{FGame.Platform, null},
|
||||
{FGame.BendGame, null},
|
||||
{FGame.TslGame, null},
|
||||
{FGame.PortalWars, null},
|
||||
{FGame.Gameface, null},
|
||||
{FGame.Athena, null},
|
||||
{FGame.MultiVersus, null},
|
||||
{FGame.Hotta, null},
|
||||
{FGame.eFootball, null}
|
||||
};
|
||||
|
|
@ -366,14 +400,20 @@ namespace FModel.Settings
|
|||
set => SetProperty(ref _overridedOptions, value);
|
||||
}
|
||||
|
||||
public IDictionary<FGame, Dictionary<string, KeyValuePair<string, string>>> OverridedMapStructTypes
|
||||
{
|
||||
get => _overridedMapStructTypes;
|
||||
set => SetProperty(ref _overridedMapStructTypes, value);
|
||||
}
|
||||
|
||||
private IDictionary<FGame, FEndpoint[]> _customEndpoints = new Dictionary<FGame, FEndpoint[]>
|
||||
{
|
||||
{FGame.Unknown, new FEndpoint[]{new (), new ()}},
|
||||
{
|
||||
FGame.FortniteGame, new []
|
||||
{
|
||||
new FEndpoint("https://fortnitecentral.gmatrixgames.ga/api/v1/aes", "$.['mainKey','dynamicKeys']"),
|
||||
new FEndpoint("https://fortnitecentral.gmatrixgames.ga/api/v1/mappings", "$.[?(@.meta.compressionMethod=='Oodle')].['url','fileName']") // && @.meta.platform=='Windows'
|
||||
new FEndpoint("https://fortnitecentral.genxgames.gg/api/v1/aes", "$.['mainKey','dynamicKeys']"),
|
||||
new FEndpoint("https://fortnitecentral.genxgames.gg/api/v1/mappings", "$.[?(@.meta.compressionMethod=='Oodle')].['url','fileName']") // && @.meta.platform=='Windows'
|
||||
}
|
||||
},
|
||||
{FGame.ShooterGame, new FEndpoint[]{new (), new ()}},
|
||||
|
|
@ -393,7 +433,7 @@ namespace FModel.Settings
|
|||
{FGame.PortalWars, new FEndpoint[]{new (), new ()}},
|
||||
{FGame.Gameface, new FEndpoint[]{new (), new ()}},
|
||||
{FGame.Athena, new FEndpoint[]{new (), new ()}},
|
||||
{FGame.PandaGame, new FEndpoint[]{new (), new ()}},
|
||||
{FGame.MultiVersus, new FEndpoint[]{new (), new ()}},
|
||||
{FGame.Hotta, new FEndpoint[]{new (), new ()}},
|
||||
{FGame.eFootball, new FEndpoint[]{new (), new ()}}
|
||||
};
|
||||
|
|
@ -471,7 +511,7 @@ namespace FModel.Settings
|
|||
{FGame.PortalWars, new List<CustomDirectory>()},
|
||||
{FGame.Gameface, new List<CustomDirectory>()},
|
||||
{FGame.Athena, new List<CustomDirectory>()},
|
||||
{FGame.PandaGame, new List<CustomDirectory>()},
|
||||
{FGame.MultiVersus, new List<CustomDirectory>()},
|
||||
{FGame.Hotta, new List<CustomDirectory>()},
|
||||
{FGame.eFootball, new List<CustomDirectory>()}
|
||||
};
|
||||
|
|
@ -614,6 +654,27 @@ namespace FModel.Settings
|
|||
set => SetProperty(ref _showGrid, value);
|
||||
}
|
||||
|
||||
private bool _animateWithRotationOnly;
|
||||
public bool AnimateWithRotationOnly
|
||||
{
|
||||
get => _animateWithRotationOnly;
|
||||
set => SetProperty(ref _animateWithRotationOnly, value);
|
||||
}
|
||||
|
||||
private Camera.WorldMode _cameraMode = Camera.WorldMode.Arcball;
|
||||
public Camera.WorldMode CameraMode
|
||||
{
|
||||
get => _cameraMode;
|
||||
set => SetProperty(ref _cameraMode, value);
|
||||
}
|
||||
|
||||
private int _previewMaxTextureSize = 1024;
|
||||
public int PreviewMaxTextureSize
|
||||
{
|
||||
get => _previewMaxTextureSize;
|
||||
set => SetProperty(ref _previewMaxTextureSize, value);
|
||||
}
|
||||
|
||||
private bool _previewStaticMeshes = true;
|
||||
public bool PreviewStaticMeshes
|
||||
{
|
||||
|
|
|
|||
|
|
@ -103,18 +103,12 @@ public class AesManagerViewModel : ViewModel
|
|||
}
|
||||
}
|
||||
|
||||
public async Task UpdateProvider(bool isLaunch)
|
||||
public void SetAesKeys()
|
||||
{
|
||||
if (!isLaunch && !HasChange) return;
|
||||
|
||||
_cue4Parse.ClearProvider();
|
||||
await _cue4Parse.LoadVfs(AesKeys);
|
||||
|
||||
if (_cue4Parse.Game == FGame.Unknown && UserSettings.Default.ManualGames.ContainsKey(UserSettings.Default.GameDirectory))
|
||||
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].AesKeys = _keysFromSettings;
|
||||
else UserSettings.Default.AesKeys[_cue4Parse.Game] = _keysFromSettings;
|
||||
|
||||
Log.Information("{@Json}", UserSettings.Default);
|
||||
// Log.Information("{@Json}", UserSettings.Default);
|
||||
}
|
||||
|
||||
private IEnumerable<FileItem> EnumerateAesKeys()
|
||||
|
|
|
|||
|
|
@ -12,14 +12,12 @@ namespace FModel.ViewModels.ApiEndpoints;
|
|||
|
||||
public class DynamicApiEndpoint : AbstractApiProvider
|
||||
{
|
||||
public DynamicApiEndpoint(RestClient client) : base(client)
|
||||
{
|
||||
}
|
||||
public DynamicApiEndpoint(RestClient client) : base(client) { }
|
||||
|
||||
public async Task<AesResponse> GetAesKeysAsync(CancellationToken token, string url, string path)
|
||||
{
|
||||
var body = await GetRequestBody(token, url).ConfigureAwait(false);
|
||||
var tokens = body.SelectTokens(path);
|
||||
var tokens = body.SelectTokens(path).ToArray();
|
||||
|
||||
var ret = new AesResponse { MainKey = Helper.FixKey(tokens.ElementAtOrDefault(0)?.ToString()) };
|
||||
if (tokens.ElementAtOrDefault(1) is JArray dynamicKeys)
|
||||
|
|
@ -32,10 +30,12 @@ public class DynamicApiEndpoint : AbstractApiProvider
|
|||
ret.DynamicKeys.Add(new DynamicKey
|
||||
{
|
||||
Name = dynamicKey["name"]?.ToString(),
|
||||
Guid = guid.ToString(), Key = Helper.FixKey(key.ToString())
|
||||
Guid = guid.ToString(),
|
||||
Key = Helper.FixKey(key.ToString())
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -47,9 +47,9 @@ public class DynamicApiEndpoint : AbstractApiProvider
|
|||
public async Task<MappingsResponse[]> GetMappingsAsync(CancellationToken token, string url, string path)
|
||||
{
|
||||
var body = await GetRequestBody(token, url).ConfigureAwait(false);
|
||||
var tokens = body.SelectTokens(path);
|
||||
var tokens = body.SelectTokens(path).ToArray();
|
||||
|
||||
var ret = new MappingsResponse[] {new()};
|
||||
var ret = new MappingsResponse[] { new() };
|
||||
ret[0].Url = tokens.ElementAtOrDefault(0)?.ToString();
|
||||
if (tokens.ElementAtOrDefault(1) is not { } fileName)
|
||||
fileName = ret[0].Url?.SubstringAfterLast("/");
|
||||
|
|
|
|||
|
|
@ -1,15 +1,10 @@
|
|||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using EpicManifestParser.Objects;
|
||||
|
||||
using FModel.Framework;
|
||||
using FModel.Settings;
|
||||
using FModel.ViewModels.ApiEndpoints.Models;
|
||||
|
||||
using RestSharp;
|
||||
|
||||
using Serilog;
|
||||
|
||||
namespace FModel.ViewModels.ApiEndpoints;
|
||||
|
|
@ -18,11 +13,10 @@ public class EpicApiEndpoint : AbstractApiProvider
|
|||
{
|
||||
private const string _OAUTH_URL = "https://account-public-service-prod03.ol.epicgames.com/account/api/oauth/token";
|
||||
private const string _BASIC_TOKEN = "basic MzQ0NmNkNzI2OTRjNGE0NDg1ZDgxYjc3YWRiYjIxNDE6OTIwOWQ0YTVlMjVhNDU3ZmI5YjA3NDg5ZDMxM2I0MWE=";
|
||||
private const string _LAUNCHER_ASSETS = "https://launcher-public-service-prod06.ol.epicgames.com/launcher/api/public/assets/v2/platform/Windows/namespace/fn/catalogItem/4fe75bbc5a674f4f9b356b5c90567da5/app/Fortnite/label/Live";
|
||||
private const string _APP_URL = "https://launcher-public-service-prod06.ol.epicgames.com/launcher/api/public/assets/v2/platform/Windows/namespace/fn/catalogItem/4fe75bbc5a674f4f9b356b5c90567da5/app/Fortnite/label/Live";
|
||||
private const string _CBM_URL = "https://launcher-public-service-prod06.ol.epicgames.com/launcher/api/public/assets/Windows/5cb97847cee34581afdbc445400e2f77/FortniteContentBuilds";
|
||||
|
||||
public EpicApiEndpoint(RestClient client) : base(client)
|
||||
{
|
||||
}
|
||||
public EpicApiEndpoint(RestClient client) : base(client) { }
|
||||
|
||||
public async Task<ManifestInfo> GetManifestAsync(CancellationToken token)
|
||||
{
|
||||
|
|
@ -35,11 +29,30 @@ public class EpicApiEndpoint : AbstractApiProvider
|
|||
}
|
||||
}
|
||||
|
||||
var request = new FRestRequest(_LAUNCHER_ASSETS);
|
||||
var request = new FRestRequest(_APP_URL);
|
||||
request.AddHeader("Authorization", $"bearer {UserSettings.Default.LastAuthResponse.AccessToken}");
|
||||
var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false);
|
||||
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, response.ResponseUri?.OriginalString);
|
||||
return new ManifestInfo(response.Content);
|
||||
return response.IsSuccessful ? new ManifestInfo(response.Content) : null;
|
||||
}
|
||||
|
||||
public async Task<ContentBuildManifestInfo> GetContentBuildManifestAsync(CancellationToken token, string label)
|
||||
{
|
||||
if (await IsExpired().ConfigureAwait(false))
|
||||
{
|
||||
var auth = await GetAuthAsync(token).ConfigureAwait(false);
|
||||
if (auth != null)
|
||||
{
|
||||
UserSettings.Default.LastAuthResponse = auth;
|
||||
}
|
||||
}
|
||||
|
||||
var request = new FRestRequest(_CBM_URL);
|
||||
request.AddHeader("Authorization", $"bearer {UserSettings.Default.LastAuthResponse.AccessToken}");
|
||||
request.AddQueryParameter("label", label);
|
||||
var response = await _client.ExecuteAsync(request, token).ConfigureAwait(false);
|
||||
Log.Information("[{Method}] [{Status}({StatusCode})] '{Resource}'", request.Method, response.StatusDescription, (int) response.StatusCode, response.ResponseUri?.OriginalString);
|
||||
return response.IsSuccessful ? new ContentBuildManifestInfo(response.Content) : null;
|
||||
}
|
||||
|
||||
public ManifestInfo GetManifest(CancellationToken token)
|
||||
|
|
@ -47,6 +60,11 @@ public class EpicApiEndpoint : AbstractApiProvider
|
|||
return GetManifestAsync(token).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public ContentBuildManifestInfo GetContentBuildManifest(CancellationToken token, string label)
|
||||
{
|
||||
return GetContentBuildManifestAsync(token, label).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
private async Task<AuthResponse> GetAuthAsync(CancellationToken token)
|
||||
{
|
||||
var request = new FRestRequest(_OAUTH_URL, Method.Post);
|
||||
|
|
|
|||
|
|
@ -29,9 +29,7 @@ public class FModelApiEndpoint : AbstractApiProvider
|
|||
private readonly IDictionary<string, CommunityDesign> _communityDesigns = new Dictionary<string, CommunityDesign>();
|
||||
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
|
||||
|
||||
public FModelApiEndpoint(RestClient client) : base(client)
|
||||
{
|
||||
}
|
||||
public FModelApiEndpoint(RestClient client) : base(client) { }
|
||||
|
||||
public async Task<News> GetNewsAsync(CancellationToken token, string game)
|
||||
{
|
||||
|
|
@ -180,7 +178,7 @@ public class FModelApiEndpoint : AbstractApiProvider
|
|||
|
||||
_applicationView.CUE4Parse.TabControl.AddTab($"Release Notes: {args.CurrentVersion}");
|
||||
_applicationView.CUE4Parse.TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("changelog");
|
||||
_applicationView.CUE4Parse.TabControl.SelectedTab.SetDocumentText(response.Content, false);
|
||||
_applicationView.CUE4Parse.TabControl.SelectedTab.SetDocumentText(response.Content, false, false);
|
||||
UserSettings.Default.ShowChangelog = false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,7 @@ namespace FModel.ViewModels.ApiEndpoints;
|
|||
|
||||
public class FortniteApiEndpoint : AbstractApiProvider
|
||||
{
|
||||
public FortniteApiEndpoint(RestClient client) : base(client)
|
||||
{
|
||||
}
|
||||
public FortniteApiEndpoint(RestClient client) : base(client) { }
|
||||
|
||||
public async Task<PlaylistResponse> GetPlaylistAsync(string playlistId)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -9,13 +9,11 @@ namespace FModel.ViewModels.ApiEndpoints;
|
|||
|
||||
public class FortniteCentralApiEndpoint : AbstractApiProvider
|
||||
{
|
||||
public FortniteCentralApiEndpoint(RestClient client) : base(client)
|
||||
{
|
||||
}
|
||||
public FortniteCentralApiEndpoint(RestClient client) : base(client) { }
|
||||
|
||||
public async Task<Dictionary<string, Dictionary<string, string>>> GetHotfixesAsync(CancellationToken token, string language = "en")
|
||||
{
|
||||
var request = new FRestRequest("https://fortnitecentral.gmatrixgames.ga/api/v1/hotfixes")
|
||||
var request = new FRestRequest("https://fortnitecentral.genxgames.gg/api/v1/hotfixes")
|
||||
{
|
||||
OnBeforeDeserialization = resp => { resp.ContentType = "application/json; charset=utf-8"; }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using CUE4Parse.UE4.Versions;
|
||||
using FModel.Creator;
|
||||
|
|
@ -39,6 +39,7 @@ public class Version
|
|||
[J] public int UeVer { get; private set; }
|
||||
[J] public Dictionary<string, int> CustomVersions { get; private set; }
|
||||
[J] public Dictionary<string, bool> Options { get; private set; }
|
||||
[J] public Dictionary<string, KeyValuePair<string, string>> MapStructTypes { get; private set; } = new();
|
||||
}
|
||||
|
||||
[DebuggerDisplay("{" + nameof(Mode) + "}")]
|
||||
|
|
@ -195,4 +196,4 @@ public class CommunityDesign
|
|||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using CUE4Parse.UE4.Exceptions;
|
||||
using CUE4Parse.UE4.Exceptions;
|
||||
using CUE4Parse.UE4.Readers;
|
||||
using FModel.Settings;
|
||||
using Ionic.Zlib;
|
||||
|
|
@ -21,9 +21,7 @@ public class ValorantApiEndpoint : AbstractApiProvider
|
|||
{
|
||||
private const string _URL = "https://fmodel.fortnite-api.com/valorant/v2/manifest";
|
||||
|
||||
public ValorantApiEndpoint(RestClient client) : base(client)
|
||||
{
|
||||
}
|
||||
public ValorantApiEndpoint(RestClient client) : base(client) { }
|
||||
|
||||
public async Task<VManifest> GetManifestAsync(CancellationToken token)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ public class ApplicationViewModel : ViewModel
|
|||
public EBuildKind Build
|
||||
{
|
||||
get => _build;
|
||||
private set
|
||||
private init
|
||||
{
|
||||
SetProperty(ref _build, value);
|
||||
RaisePropertyChanged(nameof(TitleExtra));
|
||||
|
|
@ -36,7 +36,7 @@ public class ApplicationViewModel : ViewModel
|
|||
public FStatus Status
|
||||
{
|
||||
get => _status;
|
||||
set => SetProperty(ref _status, value);
|
||||
private init => SetProperty(ref _status, value);
|
||||
}
|
||||
|
||||
public RightClickMenuCommand RightClickMenuCommand => _rightClickMenuCommand ??= new RightClickMenuCommand(this);
|
||||
|
|
@ -46,9 +46,10 @@ public class ApplicationViewModel : ViewModel
|
|||
public CopyCommand CopyCommand => _copyCommand ??= new CopyCommand(this);
|
||||
private CopyCommand _copyCommand;
|
||||
|
||||
public string InitialWindowTitle => $"FModel {UserSettings.Default.UpdateMode}";
|
||||
public string GameDisplayName => CUE4Parse.Provider.GameDisplayName ?? "Unknown";
|
||||
public string TitleExtra =>
|
||||
$"{UserSettings.Default.UpdateMode} - {CUE4Parse.Game.GetDescription()} (" + // FModel {UpdateMode} - {FGame} ({UE}) ({Build})
|
||||
$"{(CUE4Parse.Game == FGame.Unknown && UserSettings.Default.ManualGames.TryGetValue(UserSettings.Default.GameDirectory, out var settings) ? settings.OverridedGame : UserSettings.Default.OverridedGame[CUE4Parse.Game])})" +
|
||||
$"({(CUE4Parse.Game == FGame.Unknown && UserSettings.Default.ManualGames.TryGetValue(UserSettings.Default.GameDirectory, out var settings) ? settings.OverridedGame : UserSettings.Default.OverridedGame[CUE4Parse.Game])})" +
|
||||
$"{(Build != EBuildKind.Release ? $" ({Build})" : "")}";
|
||||
|
||||
public LoadingModesViewModel LoadingModes { get; }
|
||||
|
|
@ -73,6 +74,13 @@ public class ApplicationViewModel : ViewModel
|
|||
LoadingModes = new LoadingModesViewModel();
|
||||
|
||||
AvoidEmptyGameDirectoryAndSetEGame(false);
|
||||
if (UserSettings.Default.GameDirectory is null)
|
||||
{
|
||||
//If no game is selected, many things will break before a shutdown request is processed in the normal way.
|
||||
//A hard exit is preferable to an unhandled expection in this case
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
CUE4Parse = new CUE4ParseViewModel(UserSettings.Default.GameDirectory);
|
||||
CustomDirectories = new CustomDirectoriesViewModel(CUE4Parse.Game, UserSettings.Default.GameDirectory);
|
||||
SettingsView = new SettingsViewModel(CUE4Parse.Game);
|
||||
|
|
@ -97,6 +105,20 @@ public class ApplicationViewModel : ViewModel
|
|||
RestartWithWarning();
|
||||
}
|
||||
|
||||
public async Task UpdateProvider(bool isLaunch)
|
||||
{
|
||||
if (!isLaunch && !AesManager.HasChange) return;
|
||||
|
||||
CUE4Parse.ClearProvider();
|
||||
await ApplicationService.ThreadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
CUE4Parse.LoadVfs(cancellationToken, AesManager.AesKeys);
|
||||
CUE4Parse.Provider.LoadIniConfigs();
|
||||
AesManager.SetAesKeys();
|
||||
});
|
||||
RaisePropertyChanged(nameof(GameDisplayName));
|
||||
}
|
||||
|
||||
public void RestartWithWarning()
|
||||
{
|
||||
MessageBox.Show("It looks like you just changed something.\nFModel will restart to apply your changes.", "Uh oh, a restart is needed", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
|
|
@ -153,8 +175,7 @@ public class ApplicationViewModel : ViewModel
|
|||
}
|
||||
else
|
||||
{
|
||||
FLogger.AppendError();
|
||||
FLogger.AppendText("Could not download VgmStream", Constants.WHITE, true);
|
||||
FLogger.Append(ELog.Error, () => FLogger.Text("Could not download VgmStream", Constants.WHITE, true));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -186,16 +207,15 @@ public class ApplicationViewModel : ViewModel
|
|||
}
|
||||
}
|
||||
|
||||
public async Task InitImGuiSettings()
|
||||
public async Task InitImGuiSettings(bool forceDownload)
|
||||
{
|
||||
var imgui = Path.Combine(/*UserSettings.Default.OutputDirectory, ".data", */"imgui.ini");
|
||||
if (File.Exists(imgui)) return;
|
||||
if (File.Exists(imgui) && !forceDownload) 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);
|
||||
FLogger.Append(ELog.Error, () => FLogger.Text("Could not download ImGui settings", Constants.WHITE, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ using System.ComponentModel;
|
|||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using CUE4Parse.UE4.Vfs;
|
||||
using CUE4Parse.UE4.Versions;
|
||||
using CUE4Parse.UE4.VirtualFileSystem;
|
||||
using FModel.Framework;
|
||||
using FModel.Services;
|
||||
|
||||
|
|
@ -47,8 +48,8 @@ public class TreeItem : ViewModel
|
|||
private set => SetProperty(ref _mountPoint, value);
|
||||
}
|
||||
|
||||
private int _version;
|
||||
public int Version
|
||||
private FPackageFileVersion _version;
|
||||
public FPackageFileVersion Version
|
||||
{
|
||||
get => _version;
|
||||
private set => SetProperty(ref _version, value);
|
||||
|
|
@ -59,7 +60,7 @@ public class TreeItem : ViewModel
|
|||
public RangeObservableCollection<TreeItem> Folders { get; }
|
||||
public ICollectionView FoldersView { get; }
|
||||
|
||||
public TreeItem(string header, string archive, string mountPoint, int version, string pathHere)
|
||||
public TreeItem(string header, string archive, string mountPoint, FPackageFileVersion version, string pathHere)
|
||||
{
|
||||
Header = header;
|
||||
Archive = archive;
|
||||
|
|
@ -127,7 +128,7 @@ public class AssetsFolderViewModel
|
|||
if (lastNode == null)
|
||||
{
|
||||
var nodePath = builder.ToString();
|
||||
lastNode = new TreeItem(folder, item.Archive, entry.Vfs.MountPoint, entry.Vfs.Ver.Value, nodePath[..^1]);
|
||||
lastNode = new TreeItem(folder, item.Archive, entry.Vfs.MountPoint, entry.Vfs.Ver, nodePath[..^1]);
|
||||
lastNode.Folders.SetSuppressionState(true);
|
||||
lastNode.AssetsList.Assets.SetSuppressionState(true);
|
||||
parentNode.Add(lastNode);
|
||||
|
|
|
|||
|
|
@ -328,14 +328,16 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
|
|||
if (File.Exists(path))
|
||||
{
|
||||
Log.Information("{FileName} successfully saved", fileToSave.FileName);
|
||||
FLogger.AppendInformation();
|
||||
FLogger.AppendText($"Successfully saved '{fileToSave.FileName}'", Constants.WHITE, true);
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
{
|
||||
FLogger.Text("Successfully saved ", Constants.WHITE);
|
||||
FLogger.Link(fileToSave.FileName, path, true);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error("{FileName} could not be saved", fileToSave.FileName);
|
||||
FLogger.AppendError();
|
||||
FLogger.AppendText($"Could not save '{fileToSave.FileName}'", Constants.WHITE, true);
|
||||
FLogger.Append(ELog.Error, () => FLogger.Text($"Could not save '{fileToSave.FileName}'", Constants.WHITE, true));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -568,7 +570,11 @@ public class AudioPlayerViewModel : ViewModel, ISource, IDisposable
|
|||
{
|
||||
wavFilePath = string.Empty;
|
||||
var vgmFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "test.exe");
|
||||
if (!File.Exists(vgmFilePath)) return false;
|
||||
if (!File.Exists(vgmFilePath))
|
||||
{
|
||||
vgmFilePath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", "vgmstream-cli.exe");
|
||||
if (!File.Exists(vgmFilePath)) return false;
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(SelectedAudioFile.FilePath.SubstringBeforeLast("/"));
|
||||
File.WriteAllBytes(SelectedAudioFile.FilePath, SelectedAudioFile.Data);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ using System.Linq;
|
|||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using CUE4Parse.UE4.Vfs;
|
||||
using CUE4Parse.UE4.VirtualFileSystem;
|
||||
using FModel.Framework;
|
||||
using FModel.Services;
|
||||
using FModel.Settings;
|
||||
|
|
@ -103,14 +103,16 @@ public class BackupManagerViewModel : ViewModel
|
|||
if (new FileInfo(fullPath).Length > 0)
|
||||
{
|
||||
Log.Information("{FileName} successfully {Type}", fileName, type1);
|
||||
FLogger.AppendInformation();
|
||||
FLogger.AppendText($"Successfully {type1} '{fileName}'", Constants.WHITE, true);
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
{
|
||||
FLogger.Text($"Successfully {type1} ", Constants.WHITE);
|
||||
FLogger.Link(fileName, fullPath, true);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error("{FileName} could not be {Type}", fileName, type1);
|
||||
FLogger.AppendError();
|
||||
FLogger.AppendText($"Could not {type2} '{fileName}'", Constants.WHITE, true);
|
||||
FLogger.Append(ELog.Error, () => FLogger.Text($"Could not {type2} '{fileName}'", Constants.WHITE, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ 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.Solaris;
|
||||
using CUE4Parse.UE4.Assets.Exports.Verse;
|
||||
using CUE4Parse.UE4.Assets.Exports.Sound;
|
||||
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
|
|
@ -31,6 +31,7 @@ using CUE4Parse.UE4.Versions;
|
|||
using CUE4Parse.UE4.Wwise;
|
||||
using CUE4Parse_Conversion;
|
||||
using CUE4Parse_Conversion.Sounds;
|
||||
using CUE4Parse.FileProvider.Objects;
|
||||
using EpicManifestParser.Objects;
|
||||
using FModel.Creator;
|
||||
using FModel.Extensions;
|
||||
|
|
@ -46,6 +47,8 @@ using OpenTK.Windowing.Common;
|
|||
using OpenTK.Windowing.Desktop;
|
||||
using Serilog;
|
||||
using SkiaSharp;
|
||||
using UE4Config.Parsing;
|
||||
using Application = System.Windows.Application;
|
||||
|
||||
namespace FModel.ViewModels;
|
||||
|
||||
|
|
@ -53,9 +56,9 @@ public class CUE4ParseViewModel : ViewModel
|
|||
{
|
||||
private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView;
|
||||
private ApiEndpointViewModel _apiEndpointView => ApplicationService.ApiEndpointView;
|
||||
private readonly Regex _package = new(@"^(?!global|pakchunk.+optional\-).+(pak|utoc)$", // should be universal
|
||||
private readonly Regex _hiddenArchives = new(@"^(?!global|pakchunk.+(optional|ondemand)\-).+(pak|utoc)$", // should be universal
|
||||
RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
private readonly Regex _fnLive = new(@"^FortniteGame(/|\\)Content(/|\\)Paks(/|\\)(pakchunk(?:0|10.*|20.*|\w+)-WindowsClient|global)\.(pak|utoc)$",
|
||||
private readonly Regex _fnLive = new(@"^FortniteGame(/|\\)Content(/|\\)Paks(/|\\)",
|
||||
RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
|
||||
private FGame _game;
|
||||
|
|
@ -79,23 +82,30 @@ public class CUE4ParseViewModel : ViewModel
|
|||
set => SetProperty(ref _modelIsWaitingAnimation, value);
|
||||
}
|
||||
|
||||
public bool IsSnooperOpen => _snooper is { Exists: true, IsVisible: true };
|
||||
private Snooper _snooper;
|
||||
public Snooper SnooperViewer
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_snooper != null) return _snooper;
|
||||
|
||||
return Application.Current.Dispatcher.Invoke(delegate
|
||||
{
|
||||
return _snooper ??= new Snooper(GameWindowSettings.Default,
|
||||
var scale = ImGuiController.GetDpiScale();
|
||||
var htz = Snooper.GetMaxRefreshFrequency();
|
||||
return _snooper = new Snooper(
|
||||
new GameWindowSettings { RenderFrequency = htz, UpdateFrequency = htz },
|
||||
new NativeWindowSettings
|
||||
{
|
||||
Size = new OpenTK.Mathematics.Vector2i(
|
||||
Convert.ToInt32(SystemParameters.MaximizedPrimaryScreenWidth * .75),
|
||||
Convert.ToInt32(SystemParameters.MaximizedPrimaryScreenHeight * .85)),
|
||||
Convert.ToInt32(SystemParameters.MaximizedPrimaryScreenWidth * .75 * scale),
|
||||
Convert.ToInt32(SystemParameters.MaximizedPrimaryScreenHeight * .85 * scale)),
|
||||
NumberOfSamples = Constants.SAMPLES_COUNT,
|
||||
WindowBorder = WindowBorder.Resizable,
|
||||
Flags = ContextFlags.ForwardCompatible,
|
||||
Profile = ContextProfile.Core,
|
||||
Vsync = VSyncMode.Adaptive,
|
||||
APIVersion = new Version(4, 6),
|
||||
StartVisible = false,
|
||||
StartFocused = false,
|
||||
|
|
@ -110,9 +120,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
public AssetsFolderViewModel AssetsFolder { get; }
|
||||
public SearchViewModel SearchVm { get; }
|
||||
public TabControlViewModel TabControl { get; }
|
||||
public int LocalizedResourcesCount { get; set; }
|
||||
public bool HotfixedResourcesDone { get; set; }
|
||||
public int VirtualPathCount { get; set; }
|
||||
public ConfigIni BuildInfo { get; }
|
||||
|
||||
public CUE4ParseViewModel(string gameDirectory)
|
||||
{
|
||||
|
|
@ -142,10 +150,11 @@ public class CUE4ParseViewModel : ViewModel
|
|||
{
|
||||
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);
|
||||
Game = parent.ToEnum(FGame.Unknown);
|
||||
var versions = new VersionContainer(UserSettings.Default.OverridedGame[Game], UserSettings.Default.OverridedPlatform,
|
||||
customVersions: UserSettings.Default.OverridedCustomVersions[Game],
|
||||
optionOverrides: UserSettings.Default.OverridedOptions[Game]);
|
||||
optionOverrides: UserSettings.Default.OverridedOptions[Game],
|
||||
mapStructTypesOverrides: UserSettings.Default.OverridedMapStructTypes[Game]);
|
||||
|
||||
switch (Game)
|
||||
{
|
||||
|
|
@ -177,7 +186,8 @@ public class CUE4ParseViewModel : ViewModel
|
|||
{
|
||||
versions = new VersionContainer(settings.OverridedGame, UserSettings.Default.OverridedPlatform,
|
||||
customVersions: settings.OverridedCustomVersions,
|
||||
optionOverrides: settings.OverridedOptions);
|
||||
optionOverrides: settings.OverridedOptions,
|
||||
mapStructTypesOverrides: settings.OverridedMapStructTypes);
|
||||
goto default;
|
||||
}
|
||||
default:
|
||||
|
|
@ -190,11 +200,13 @@ public class CUE4ParseViewModel : ViewModel
|
|||
break;
|
||||
}
|
||||
}
|
||||
Provider.ReadScriptData = UserSettings.Default.ReadScriptData;
|
||||
|
||||
GameDirectory = new GameDirectoryViewModel();
|
||||
AssetsFolder = new AssetsFolderViewModel();
|
||||
SearchVm = new SearchViewModel();
|
||||
TabControl = new TabControlViewModel();
|
||||
BuildInfo = new ConfigIni(nameof(BuildInfo));
|
||||
}
|
||||
|
||||
public async Task Initialize()
|
||||
|
|
@ -235,18 +247,19 @@ public class CUE4ParseViewModel : ViewModel
|
|||
|
||||
foreach (var fileManifest in manifest.FileManifests)
|
||||
{
|
||||
if (fileManifest.Name.Equals("Cloud/BuildInfo.ini", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
BuildInfo.Read(new StreamReader(fileManifest.GetStream()));
|
||||
continue;
|
||||
}
|
||||
if (!_fnLive.IsMatch(fileManifest.Name)) continue;
|
||||
|
||||
//var casStream = manifest.FileManifests.FirstOrDefault(x => x.Name.Equals(fileManifest.Name.Replace(".utoc", ".ucas")));
|
||||
//p.Initialize(fileManifest.Name, new[] {fileManifest.GetStream(), casStream.GetStream()});
|
||||
p.Initialize(fileManifest.Name, new Stream[] { fileManifest.GetStream() }
|
||||
, it => new FStreamArchive(it, manifest.FileManifests.First(x => x.Name.Equals(it)).GetStream(), p.Versions));
|
||||
}
|
||||
|
||||
FLogger.AppendInformation();
|
||||
FLogger.AppendText($"Fortnite has been loaded successfully in {manifest.ParseTime.TotalMilliseconds}ms", Constants.WHITE, true);
|
||||
FLogger.AppendWarning();
|
||||
FLogger.AppendText($"Mappings must match '{manifest.BuildVersion}' in order to avoid errors", Constants.WHITE, true);
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
FLogger.Text($"Fortnite has been loaded successfully in {manifest.ParseTime.TotalMilliseconds}ms", Constants.WHITE, true));
|
||||
break;
|
||||
}
|
||||
case "ValorantLive":
|
||||
|
|
@ -262,8 +275,8 @@ public class CUE4ParseViewModel : ViewModel
|
|||
p.Initialize(manifestInfo.Paks[i].GetFullName(), new[] { manifestInfo.GetPakStream(i) });
|
||||
}
|
||||
|
||||
FLogger.AppendInformation();
|
||||
FLogger.AppendText($"Valorant '{manifestInfo.Header.GameVersion}' has been loaded successfully", Constants.WHITE, true);
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
FLogger.Text($"Valorant '{manifestInfo.Header.GameVersion}' has been loaded successfully", Constants.WHITE, true));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -271,13 +284,16 @@ public class CUE4ParseViewModel : ViewModel
|
|||
break;
|
||||
case DefaultFileProvider d:
|
||||
d.Initialize();
|
||||
|
||||
var buildInfoPath = Path.Combine(UserSettings.Default.GameDirectory, "..\\..\\..\\Cloud\\BuildInfo.ini");
|
||||
if (File.Exists(buildInfoPath)) BuildInfo.Read(new StringReader(File.ReadAllText(buildInfoPath)));
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (var vfs in Provider.UnloadedVfs) // push files from the provider to the ui
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (vfs.Length <= 365 || !_package.IsMatch(vfs.Name)) continue;
|
||||
if (vfs.Length <= 365 || !_hiddenArchives.IsMatch(vfs.Name)) continue;
|
||||
|
||||
GameDirectory.Add(vfs);
|
||||
}
|
||||
|
|
@ -288,41 +304,38 @@ public class CUE4ParseViewModel : ViewModel
|
|||
/// load virtual files system from GameDirectory
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task LoadVfs(IEnumerable<FileItem> aesKeys)
|
||||
public void LoadVfs(CancellationToken token, IEnumerable<FileItem> aesKeys)
|
||||
{
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
GameDirectory.DeactivateAll();
|
||||
|
||||
// load files using UnloadedVfs to include non-encrypted vfs
|
||||
foreach (var key in aesKeys)
|
||||
{
|
||||
GameDirectory.DeactivateAll();
|
||||
token.ThrowIfCancellationRequested(); // cancel if needed
|
||||
|
||||
// load files using UnloadedVfs to include non-encrypted vfs
|
||||
foreach (var key in aesKeys)
|
||||
var k = key.Key.Trim();
|
||||
if (k.Length != 66) k = Constants.ZERO_64_CHAR;
|
||||
Provider.SubmitKey(key.Guid, new FAesKey(k));
|
||||
}
|
||||
|
||||
// files in MountedVfs will be enabled
|
||||
foreach (var file in GameDirectory.DirectoryFiles)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
if (Provider.MountedVfs.FirstOrDefault(x => x.Name == file.Name) is not { } vfs)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested(); // cancel if needed
|
||||
if (Provider.UnloadedVfs.FirstOrDefault(x => x.Name == file.Name) is IoStoreReader store)
|
||||
file.FileCount = (int) store.Info.TocEntryCount - 1;
|
||||
|
||||
var k = key.Key.Trim();
|
||||
if (k.Length != 66) k = Constants.ZERO_64_CHAR;
|
||||
Provider.SubmitKey(key.Guid, new FAesKey(k));
|
||||
continue;
|
||||
}
|
||||
|
||||
// files in MountedVfs will be enabled
|
||||
foreach (var file in GameDirectory.DirectoryFiles)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (Provider.MountedVfs.FirstOrDefault(x => x.Name == file.Name) is not { } vfs)
|
||||
{
|
||||
if (Provider.UnloadedVfs.FirstOrDefault(x => x.Name == file.Name) is IoStoreReader store)
|
||||
file.FileCount = (int) store.Info.TocEntryCount - 1;
|
||||
file.IsEnabled = true;
|
||||
file.MountPoint = vfs.MountPoint;
|
||||
file.FileCount = vfs.FileCount;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
file.IsEnabled = true;
|
||||
file.MountPoint = vfs.MountPoint;
|
||||
file.FileCount = vfs.FileCount;
|
||||
}
|
||||
|
||||
Game = Helper.IAmThePanda(Provider.GameName) ? FGame.PandaGame : Provider.GameName.ToEnum(Game);
|
||||
});
|
||||
Game = Provider.GameName.ToEnum(Game);
|
||||
}
|
||||
|
||||
public void ClearProvider()
|
||||
|
|
@ -332,7 +345,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
AssetsFolder.Folders.Clear();
|
||||
SearchVm.SearchResults.Clear();
|
||||
Helper.CloseWindow<AdonisWindow>("Search View");
|
||||
Provider.UnloadAllVfs();
|
||||
Provider.UnloadNonStreamedVfs();
|
||||
GC.Collect();
|
||||
}
|
||||
|
||||
|
|
@ -359,33 +372,36 @@ public class CUE4ParseViewModel : ViewModel
|
|||
var info = _apiEndpointView.FModelApi.GetNews(cancellationToken, Provider.GameName);
|
||||
if (info == null) return;
|
||||
|
||||
for (var i = 0; i < info.Messages.Length; i++)
|
||||
FLogger.Append(ELog.None, () =>
|
||||
{
|
||||
FLogger.AppendText(info.Messages[i], info.Colors[i], bool.Parse(info.NewLines[i]));
|
||||
}
|
||||
for (var i = 0; i < info.Messages.Length; i++)
|
||||
{
|
||||
FLogger.Text(info.Messages[i], info.Colors[i], bool.Parse(info.NewLines[i]));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public async Task InitMappings()
|
||||
public Task InitMappings()
|
||||
{
|
||||
if (!UserSettings.IsEndpointValid(Game, EEndpointType.Mapping, out var endpoint))
|
||||
{
|
||||
Provider.MappingsContainer = null;
|
||||
return;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
return Task.Run(() =>
|
||||
{
|
||||
if (endpoint.Overwrite && File.Exists(endpoint.FilePath))
|
||||
{
|
||||
Provider.MappingsContainer = new FileUsmapTypeMappingsProvider(endpoint.FilePath);
|
||||
FLogger.AppendInformation();
|
||||
FLogger.AppendText($"Mappings pulled from '{endpoint.FilePath.SubstringAfterLast("\\")}'", Constants.WHITE, true);
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
FLogger.Text($"Mappings pulled from '{endpoint.FilePath.SubstringAfterLast("\\")}'", Constants.WHITE, true));
|
||||
}
|
||||
else if (endpoint.IsValid)
|
||||
{
|
||||
var mappingsFolder = Path.Combine(UserSettings.Default.OutputDirectory, ".data");
|
||||
var mappings = _apiEndpointView.DynamicApi.GetMappings(cancellationToken, endpoint.Url, endpoint.Path);
|
||||
var mappings = _apiEndpointView.DynamicApi.GetMappings(default, endpoint.Url, endpoint.Path);
|
||||
if (mappings is { Length: > 0 })
|
||||
{
|
||||
foreach (var mapping in mappings)
|
||||
|
|
@ -399,8 +415,8 @@ public class CUE4ParseViewModel : ViewModel
|
|||
}
|
||||
|
||||
Provider.MappingsContainer = new FileUsmapTypeMappingsProvider(mappingPath);
|
||||
FLogger.AppendInformation();
|
||||
FLogger.AppendText($"Mappings pulled from '{mapping.FileName}'", Constants.WHITE, true);
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
FLogger.Text($"Mappings pulled from '{mapping.FileName}'", Constants.WHITE, true));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -412,61 +428,154 @@ public class CUE4ParseViewModel : ViewModel
|
|||
|
||||
var latestUsmapInfo = latestUsmaps.OrderBy(f => f.LastWriteTime).Last();
|
||||
Provider.MappingsContainer = new FileUsmapTypeMappingsProvider(latestUsmapInfo.FullName);
|
||||
FLogger.AppendWarning();
|
||||
FLogger.AppendText($"Mappings pulled from '{latestUsmapInfo.Name}'", Constants.WHITE, true);
|
||||
FLogger.Append(ELog.Warning, () =>
|
||||
FLogger.Text($"Mappings pulled from '{latestUsmapInfo.Name}'", Constants.WHITE, true));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async Task LoadLocalizedResources()
|
||||
private bool _cvaVerifDone { get; set; }
|
||||
public Task VerifyConsoleVariables()
|
||||
{
|
||||
await LoadGameLocalizedResources();
|
||||
await LoadHotfixedLocalizedResources();
|
||||
await _threadWorkerView.Begin(_ => Utils.Typefaces = new Typefaces(this));
|
||||
if (LocalizedResourcesCount > 0)
|
||||
{
|
||||
FLogger.AppendInformation();
|
||||
FLogger.AppendText($"{LocalizedResourcesCount} localized resources loaded for '{UserSettings.Default.AssetLanguage.GetDescription()}'", Constants.WHITE, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
FLogger.AppendWarning();
|
||||
FLogger.AppendText($"Could not load localized resources in '{UserSettings.Default.AssetLanguage.GetDescription()}', language may not exist", Constants.WHITE, true);
|
||||
}
|
||||
}
|
||||
if (_cvaVerifDone)
|
||||
return Task.CompletedTask;
|
||||
|
||||
private async Task LoadGameLocalizedResources()
|
||||
{
|
||||
if (LocalizedResourcesCount > 0) return;
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
return Task.Run(() =>
|
||||
{
|
||||
LocalizedResourcesCount = Provider.LoadLocalization(UserSettings.Default.AssetLanguage, cancellationToken);
|
||||
var inst = new List<InstructionToken>();
|
||||
Provider.DefaultEngine.FindPropertyInstructions("ConsoleVariables", "a.StripAdditiveRefPose", inst);
|
||||
if (inst.Count > 0 && inst[0].Value.Equals("1"))
|
||||
{
|
||||
FLogger.Append(ELog.Warning, () =>
|
||||
FLogger.Text("Additive animations have their reference pose stripped, which will lead to inaccurate preview and export", Constants.WHITE, true));
|
||||
}
|
||||
_cvaVerifDone = true;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load hotfixed localized resources
|
||||
/// </summary>
|
||||
/// <remarks>Functions only when LoadLocalizedResources is used prior to this.</remarks>
|
||||
private async Task LoadHotfixedLocalizedResources()
|
||||
private int _vfcCount { get; set; }
|
||||
public Task VerifyVirtualCache()
|
||||
{
|
||||
if (Game != FGame.FortniteGame || HotfixedResourcesDone) return;
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
if (Provider is StreamedFileProvider { LiveGame: "FortniteLive" } || _vfcCount > 0)
|
||||
return Task.CompletedTask;
|
||||
|
||||
return Task.Run(() =>
|
||||
{
|
||||
var hotfixes = ApplicationService.ApiEndpointView.CentralApi.GetHotfixes(cancellationToken, Provider.GetLanguageCode(UserSettings.Default.AssetLanguage));
|
||||
_vfcCount = Provider.LoadVirtualCache();
|
||||
if (_vfcCount > 0)
|
||||
FLogger.Append(ELog.Information,
|
||||
() => FLogger.Text($"{_vfcCount} cached packages loaded", Constants.WHITE, true));
|
||||
});
|
||||
}
|
||||
|
||||
public Task VerifyContentBuildManifest()
|
||||
{
|
||||
if (Provider is not DefaultFileProvider || !Provider.GameName.Equals("FortniteGame", StringComparison.OrdinalIgnoreCase))
|
||||
return Task.CompletedTask;
|
||||
|
||||
var persistentDownloadDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "FortniteGame/Saved/PersistentDownloadDir");
|
||||
var vfcMetadata = Path.Combine(persistentDownloadDir, "VFC", "vfc.meta");
|
||||
if (!File.Exists(vfcMetadata))
|
||||
return Task.CompletedTask;
|
||||
|
||||
// load if local fortnite with ondemand disabled
|
||||
// VFC folder is created at launch if ondemand
|
||||
// VFC folder is deleted at launch if not ondemand anymore
|
||||
return Task.Run(() =>
|
||||
{
|
||||
var inst = new List<InstructionToken>();
|
||||
BuildInfo.FindPropertyInstructions("Content", "Label", inst);
|
||||
if (inst.Count <= 0) return;
|
||||
|
||||
var manifestInfo = _apiEndpointView.EpicApi.GetContentBuildManifest(default, inst[0].Value);
|
||||
var manifestDir = new DirectoryInfo(Path.Combine(persistentDownloadDir, "ManifestCache"));
|
||||
var manifestPath = Path.Combine(manifestDir.FullName, manifestInfo?.FileName ?? "");
|
||||
|
||||
byte[] manifestData;
|
||||
if (File.Exists(manifestPath))
|
||||
{
|
||||
manifestData = File.ReadAllBytes(manifestPath);
|
||||
}
|
||||
else if (manifestInfo != null)
|
||||
{
|
||||
manifestData = manifestInfo.DownloadManifestData();
|
||||
File.WriteAllBytes(manifestPath, manifestData);
|
||||
}
|
||||
else if (manifestDir.Exists && manifestDir.GetFiles("*.manifest") is { Length: > 0} cachedManifests)
|
||||
{
|
||||
manifestData = File.ReadAllBytes(cachedManifests[0].FullName);
|
||||
}
|
||||
else return;
|
||||
|
||||
var manifest = new Manifest(manifestData, new ManifestOptions
|
||||
{
|
||||
ChunkBaseUri = new Uri("http://epicgames-download1.akamaized.net/Builds/Fortnite/Content/CloudDir/ChunksV4/", UriKind.Absolute),
|
||||
ChunkCacheDirectory = Directory.CreateDirectory(Path.Combine(UserSettings.Default.OutputDirectory, ".data"))
|
||||
});
|
||||
|
||||
var onDemandFiles = new Dictionary<string, GameFile>();
|
||||
foreach (var fileManifest in manifest.FileManifests)
|
||||
{
|
||||
if (Provider.Files.TryGetValue(fileManifest.Name, out _)) continue;
|
||||
|
||||
var onDemandFile = new StreamedGameFile(fileManifest.Name, fileManifest.GetStream(), Provider.Versions);
|
||||
if (Provider.IsCaseInsensitive) onDemandFiles[onDemandFile.Path.ToLowerInvariant()] = onDemandFile;
|
||||
else onDemandFiles[onDemandFile.Path] = onDemandFile;
|
||||
}
|
||||
|
||||
(Provider.Files as FileProviderDictionary)?.AddFiles(onDemandFiles);
|
||||
if (onDemandFiles.Count > 0)
|
||||
FLogger.Append(ELog.Information,
|
||||
() => FLogger.Text($"{onDemandFiles.Count} streamed packages loaded", Constants.WHITE, true));
|
||||
#if DEBUG
|
||||
|
||||
var missing = manifest.FileManifests.Count - onDemandFiles.Count;
|
||||
if (missing > 0)
|
||||
FLogger.Append(ELog.Debug,
|
||||
() => FLogger.Text($"{missing} packages were already loaded by regular archives", Constants.WHITE, true));
|
||||
#endif
|
||||
});
|
||||
}
|
||||
|
||||
public int LocalizedResourcesCount { get; set; }
|
||||
public bool LocalResourcesDone { get; set; }
|
||||
public bool HotfixedResourcesDone { get; set; }
|
||||
public async Task LoadLocalizedResources()
|
||||
{
|
||||
var snapshot = LocalizedResourcesCount;
|
||||
await Task.WhenAll(LoadGameLocalizedResources(), LoadHotfixedLocalizedResources()).ConfigureAwait(false);
|
||||
if (snapshot != LocalizedResourcesCount)
|
||||
{
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
FLogger.Text($"{LocalizedResourcesCount} localized resources loaded for '{UserSettings.Default.AssetLanguage.GetDescription()}'", Constants.WHITE, true));
|
||||
}
|
||||
}
|
||||
private Task LoadGameLocalizedResources()
|
||||
{
|
||||
if (LocalResourcesDone) return Task.CompletedTask;
|
||||
return Task.Run(() =>
|
||||
{
|
||||
LocalizedResourcesCount += Provider.LoadLocalization(UserSettings.Default.AssetLanguage);
|
||||
LocalResourcesDone = true;
|
||||
});
|
||||
}
|
||||
private Task LoadHotfixedLocalizedResources()
|
||||
{
|
||||
if (!Provider.GameName.Equals("fortnitegame", StringComparison.OrdinalIgnoreCase) || HotfixedResourcesDone) return Task.CompletedTask;
|
||||
return Task.Run(() =>
|
||||
{
|
||||
var hotfixes = ApplicationService.ApiEndpointView.CentralApi.GetHotfixes(default, Provider.GetLanguageCode(UserSettings.Default.AssetLanguage));
|
||||
if (hotfixes == null) return;
|
||||
|
||||
HotfixedResourcesDone = true;
|
||||
foreach (var entries in hotfixes)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (!Provider.LocalizedResources.ContainsKey(entries.Key))
|
||||
Provider.LocalizedResources[entries.Key] = new Dictionary<string, string>();
|
||||
|
||||
foreach (var keyValue in entries.Value)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
Provider.LocalizedResources[entries.Key][keyValue.Key] = keyValue.Value;
|
||||
LocalizedResourcesCount++;
|
||||
}
|
||||
|
|
@ -474,21 +583,22 @@ public class CUE4ParseViewModel : ViewModel
|
|||
});
|
||||
}
|
||||
|
||||
public async Task LoadVirtualPaths()
|
||||
private int _virtualPathCount { get; set; }
|
||||
public Task LoadVirtualPaths()
|
||||
{
|
||||
if (VirtualPathCount > 0) return;
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
if (_virtualPathCount > 0) return Task.CompletedTask;
|
||||
return Task.Run(() =>
|
||||
{
|
||||
VirtualPathCount = Provider.LoadVirtualPaths(UserSettings.Default.OverridedGame[Game].GetVersion(), cancellationToken);
|
||||
if (VirtualPathCount > 0)
|
||||
_virtualPathCount = Provider.LoadVirtualPaths(UserSettings.Default.OverridedGame[Game].GetVersion());
|
||||
if (_virtualPathCount > 0)
|
||||
{
|
||||
FLogger.AppendInformation();
|
||||
FLogger.AppendText($"{VirtualPathCount} virtual paths loaded", Constants.WHITE, true);
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
FLogger.Text($"{_virtualPathCount} virtual paths loaded", Constants.WHITE, true));
|
||||
}
|
||||
else
|
||||
{
|
||||
FLogger.AppendWarning();
|
||||
FLogger.AppendText("Could not load virtual paths, plugin manifest may not exist", Constants.WHITE, true);
|
||||
FLogger.Append(ELog.Warning, () =>
|
||||
FLogger.Text("Could not load virtual paths, plugin manifest may not exist", Constants.WHITE, true));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -497,7 +607,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
{
|
||||
foreach (var asset in assetItems)
|
||||
{
|
||||
Thread.Sleep(10);
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
Extract(cancellationToken, asset.FullPath, TabControl.HasNoTabs);
|
||||
}
|
||||
|
|
@ -507,7 +617,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
{
|
||||
foreach (var asset in folder.AssetsList.Assets)
|
||||
{
|
||||
Thread.Sleep(10);
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
try
|
||||
{
|
||||
|
|
@ -523,7 +633,15 @@ public class CUE4ParseViewModel : ViewModel
|
|||
}
|
||||
|
||||
public void ExportFolder(CancellationToken cancellationToken, TreeItem folder)
|
||||
=> BulkFolder(cancellationToken, folder, asset => ExportData(asset.FullPath));
|
||||
{
|
||||
Parallel.ForEach(folder.AssetsList.Assets, asset =>
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
ExportData(asset.FullPath, false);
|
||||
});
|
||||
|
||||
foreach (var f in folder.Folders) ExportFolder(cancellationToken, f);
|
||||
}
|
||||
|
||||
public void ExtractFolder(CancellationToken cancellationToken, TreeItem folder)
|
||||
=> BulkFolder(cancellationToken, folder, asset => Extract(cancellationToken, asset.FullPath, TabControl.HasNoTabs));
|
||||
|
|
@ -558,8 +676,9 @@ public class CUE4ParseViewModel : ViewModel
|
|||
TabControl.SelectedTab.Directory = directory;
|
||||
}
|
||||
|
||||
var autoProperties = bulk == (EBulkType.Properties | EBulkType.Auto);
|
||||
var autoTextures = bulk == (EBulkType.Textures | EBulkType.Auto);
|
||||
var updateUi = !HasFlag(bulk, EBulkType.Auto);
|
||||
var saveProperties = HasFlag(bulk, EBulkType.Properties);
|
||||
var saveTextures = HasFlag(bulk, EBulkType.Textures);
|
||||
TabControl.SelectedTab.ClearImages();
|
||||
TabControl.SelectedTab.ResetDocumentText();
|
||||
TabControl.SelectedTab.ScrollTrigger = null;
|
||||
|
|
@ -569,8 +688,8 @@ public class CUE4ParseViewModel : ViewModel
|
|||
case "uasset":
|
||||
case "umap":
|
||||
{
|
||||
var exports = Provider.LoadObjectExports(fullPath); // cancellationToken
|
||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(exports, Formatting.Indented), autoProperties);
|
||||
var exports = Provider.LoadAllObjects(fullPath);
|
||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(exports, Formatting.Indented), saveProperties, updateUi);
|
||||
if (HasFlag(bulk, EBulkType.Properties)) break; // do not search for viewable exports if we are dealing with jsons
|
||||
|
||||
foreach (var e in exports)
|
||||
|
|
@ -586,6 +705,8 @@ public class CUE4ParseViewModel : ViewModel
|
|||
case "manifest":
|
||||
case "uplugin":
|
||||
case "archive":
|
||||
case "vmodule":
|
||||
case "verse":
|
||||
case "html":
|
||||
case "json":
|
||||
case "ini":
|
||||
|
|
@ -611,7 +732,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
using var stream = new MemoryStream(data) { Position = 0 };
|
||||
using var reader = new StreamReader(stream);
|
||||
|
||||
TabControl.SelectedTab.SetDocumentText(reader.ReadToEnd(), autoProperties);
|
||||
TabControl.SelectedTab.SetDocumentText(reader.ReadToEnd(), saveProperties, updateUi);
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -621,7 +742,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
if (Provider.TryCreateReader(fullPath, out var archive))
|
||||
{
|
||||
var metadata = new FTextLocalizationMetaDataResource(archive);
|
||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(metadata, Formatting.Indented), autoProperties);
|
||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(metadata, Formatting.Indented), saveProperties, updateUi);
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -631,7 +752,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
if (Provider.TryCreateReader(fullPath, out var archive))
|
||||
{
|
||||
var locres = new FTextLocalizationResource(archive);
|
||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(locres, Formatting.Indented), autoProperties);
|
||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(locres, Formatting.Indented), saveProperties, updateUi);
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -641,7 +762,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
if (Provider.TryCreateReader(fullPath, out var archive))
|
||||
{
|
||||
var registry = new FAssetRegistryState(archive);
|
||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(registry, Formatting.Indented), autoProperties);
|
||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(registry, Formatting.Indented), saveProperties, updateUi);
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -652,7 +773,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
if (Provider.TryCreateReader(fullPath, out var archive))
|
||||
{
|
||||
var wwise = new WwiseReader(archive);
|
||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(wwise, Formatting.Indented), autoProperties);
|
||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(wwise, Formatting.Indented), saveProperties, updateUi);
|
||||
foreach (var (name, data) in wwise.WwiseEncodedMedias)
|
||||
{
|
||||
SaveAndPlaySound(fullPath.SubstringBeforeWithLast("/") + name, "WEM", data);
|
||||
|
|
@ -673,7 +794,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
if (Provider.TryCreateReader(fullPath, out var archive))
|
||||
{
|
||||
var header = new FOodleDictionaryArchive(archive).Header;
|
||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(header, Formatting.Indented), autoProperties);
|
||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(header, Formatting.Indented), saveProperties, updateUi);
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -685,7 +806,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
if (Provider.TrySaveAsset(fullPath, out var data))
|
||||
{
|
||||
using var stream = new MemoryStream(data) { Position = 0 };
|
||||
TabControl.SelectedTab.AddImage(fileName.SubstringBeforeLast("."), false, SKBitmap.Decode(stream), autoTextures);
|
||||
TabControl.SelectedTab.AddImage(fileName.SubstringBeforeLast("."), false, SKBitmap.Decode(stream), saveTextures, updateUi);
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -705,7 +826,7 @@ public class CUE4ParseViewModel : ViewModel
|
|||
canvas.DrawPicture(svg.Picture, paint);
|
||||
}
|
||||
|
||||
TabControl.SelectedTab.AddImage(fileName.SubstringBeforeLast("."), false, bitmap, autoTextures);
|
||||
TabControl.SelectedTab.AddImage(fileName.SubstringBeforeLast("."), false, bitmap, saveTextures, updateUi);
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -713,8 +834,8 @@ public class CUE4ParseViewModel : ViewModel
|
|||
case "ufont":
|
||||
case "otf":
|
||||
case "ttf":
|
||||
FLogger.AppendWarning();
|
||||
FLogger.AppendText($"Export '{fileName}' raw data and change its extension if you want it to be an installable font file", Constants.WHITE, true);
|
||||
FLogger.Append(ELog.Warning, () =>
|
||||
FLogger.Text($"Export '{fileName}' raw data and change its extension if you want it to be an installable font file", Constants.WHITE, true));
|
||||
break;
|
||||
case "ushaderbytecode":
|
||||
case "ushadercode":
|
||||
|
|
@ -722,15 +843,15 @@ public class CUE4ParseViewModel : ViewModel
|
|||
if (Provider.TryCreateReader(fullPath, out var archive))
|
||||
{
|
||||
var ar = new FShaderCodeArchive(archive);
|
||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(ar, Formatting.Indented), autoProperties);
|
||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(ar, Formatting.Indented), saveProperties, updateUi);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
FLogger.AppendWarning();
|
||||
FLogger.AppendText($"The package '{fileName}' is of an unknown type.", Constants.WHITE, true);
|
||||
FLogger.Append(ELog.Warning, () =>
|
||||
FLogger.Text($"The package '{fileName}' is of an unknown type.", Constants.WHITE, true));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -742,9 +863,9 @@ public class CUE4ParseViewModel : ViewModel
|
|||
TabControl.AddTab(fullPath.SubstringAfterLast('/'), fullPath.SubstringBeforeLast('/'));
|
||||
TabControl.SelectedTab.ScrollTrigger = objectName;
|
||||
|
||||
var exports = Provider.LoadObjectExports(fullPath); // cancellationToken
|
||||
var exports = Provider.LoadAllObjects(fullPath);
|
||||
TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector(""); // json
|
||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(exports, Formatting.Indented), false);
|
||||
TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(exports, Formatting.Indented), false, false);
|
||||
|
||||
foreach (var e in exports)
|
||||
{
|
||||
|
|
@ -756,21 +877,22 @@ public class CUE4ParseViewModel : ViewModel
|
|||
private bool CheckExport(CancellationToken cancellationToken, UObject export, EBulkType bulk = EBulkType.None) // return true once you wanna stop searching for exports
|
||||
{
|
||||
var isNone = bulk == EBulkType.None;
|
||||
var loadTextures = isNone || HasFlag(bulk, EBulkType.Textures);
|
||||
var updateUi = !HasFlag(bulk, EBulkType.Auto);
|
||||
var saveTextures = HasFlag(bulk, EBulkType.Textures);
|
||||
switch (export)
|
||||
{
|
||||
case USolarisDigest solarisDigest when isNone:
|
||||
case UVerseDigest verseDigest when isNone:
|
||||
{
|
||||
if (!TabControl.CanAddTabs) return false;
|
||||
|
||||
TabControl.AddTab($"{solarisDigest.ProjectName}.ulang");
|
||||
TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("ulang");
|
||||
TabControl.SelectedTab.SetDocumentText(solarisDigest.ReadableCode, false);
|
||||
TabControl.AddTab($"{verseDigest.ProjectName}.verse");
|
||||
TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("verse");
|
||||
TabControl.SelectedTab.SetDocumentText(verseDigest.ReadableCode, false, false);
|
||||
return true;
|
||||
}
|
||||
case UTexture2D texture when loadTextures:
|
||||
case UTexture2D { IsVirtual: false } texture when isNone:
|
||||
{
|
||||
TabControl.SelectedTab.AddImage(texture, HasFlag(bulk, EBulkType.Auto));
|
||||
TabControl.SelectedTab.AddImage(texture, saveTextures, updateUi);
|
||||
return false;
|
||||
}
|
||||
case UAkMediaAssetData when isNone:
|
||||
|
|
@ -802,9 +924,11 @@ public class CUE4ParseViewModel : ViewModel
|
|||
SnooperViewer.Run();
|
||||
return true;
|
||||
}
|
||||
case UAnimSequence a when isNone && ModelIsWaitingAnimation:
|
||||
case UAnimSequence when isNone && ModelIsWaitingAnimation:
|
||||
case UAnimMontage when isNone && ModelIsWaitingAnimation:
|
||||
case UAnimComposite when isNone && ModelIsWaitingAnimation:
|
||||
{
|
||||
SnooperViewer.Renderer.Animate(a);
|
||||
SnooperViewer.Renderer.Animate(export);
|
||||
SnooperViewer.Run();
|
||||
return true;
|
||||
}
|
||||
|
|
@ -813,22 +937,24 @@ public class CUE4ParseViewModel : ViewModel
|
|||
case USkeleton when UserSettings.Default.SaveSkeletonAsMesh && HasFlag(bulk, EBulkType.Meshes):
|
||||
// case UMaterialInstance when HasFlag(bulk, EBulkType.Materials): // read the fucking json
|
||||
case UAnimSequence when HasFlag(bulk, EBulkType.Animations):
|
||||
case UAnimMontage when HasFlag(bulk, EBulkType.Animations):
|
||||
case UAnimComposite when HasFlag(bulk, EBulkType.Animations):
|
||||
{
|
||||
SaveExport(export, HasFlag(bulk, EBulkType.Auto));
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
{
|
||||
if (!loadTextures)
|
||||
return false;
|
||||
if (!isNone && !saveTextures) return false;
|
||||
|
||||
using var package = new CreatorPackage(export, UserSettings.Default.CosmeticStyle);
|
||||
if (!package.TryConstructCreator(out var creator))
|
||||
return false;
|
||||
|
||||
creator.ParseForInfo();
|
||||
TabControl.SelectedTab.AddImage(export.Name, false, creator.Draw(), HasFlag(bulk, EBulkType.Auto));
|
||||
TabControl.SelectedTab.AddImage(export.Name, false, creator.Draw(), saveTextures, updateUi);
|
||||
return true;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -888,38 +1014,51 @@ public class CUE4ParseViewModel : ViewModel
|
|||
if (toSave.TryWriteToDir(toSaveDirectory, out var label, out var savedFilePath))
|
||||
{
|
||||
Log.Information("Successfully saved {FilePath}", savedFilePath);
|
||||
FLogger.AppendInformation();
|
||||
FLogger.AppendText($"Successfully saved {label}", Constants.WHITE, true);
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
{
|
||||
FLogger.Text("Successfully saved ", Constants.WHITE);
|
||||
FLogger.Link(label, savedFilePath, true);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warning("{FileName} could not be saved", export.Name);
|
||||
FLogger.AppendWarning();
|
||||
FLogger.AppendText($"Could not save '{export.Name}'", Constants.WHITE, true);
|
||||
Log.Error("{FileName} could not be saved", export.Name);
|
||||
FLogger.Append(ELog.Error, () => FLogger.Text($"Could not save '{export.Name}'", Constants.WHITE, true));
|
||||
}
|
||||
}
|
||||
|
||||
public void ExportData(string fullPath)
|
||||
private readonly object _rawData = new ();
|
||||
public void ExportData(string fullPath, bool updateUi = true)
|
||||
{
|
||||
var fileName = fullPath.SubstringAfterLast('/');
|
||||
if (Provider.TrySavePackage(fullPath, out var assets))
|
||||
{
|
||||
foreach (var kvp in assets)
|
||||
string path = UserSettings.Default.RawDataDirectory;
|
||||
Parallel.ForEach(assets, kvp =>
|
||||
{
|
||||
var path = Path.Combine(UserSettings.Default.RawDataDirectory, UserSettings.Default.KeepDirectoryStructure ? kvp.Key : kvp.Key.SubstringAfterLast('/')).Replace('\\', '/');
|
||||
Directory.CreateDirectory(path.SubstringBeforeLast('/'));
|
||||
File.WriteAllBytes(path, kvp.Value);
|
||||
}
|
||||
lock (_rawData)
|
||||
{
|
||||
path = Path.Combine(UserSettings.Default.RawDataDirectory, UserSettings.Default.KeepDirectoryStructure ? kvp.Key : kvp.Key.SubstringAfterLast('/')).Replace('\\', '/');
|
||||
Directory.CreateDirectory(path.SubstringBeforeLast('/'));
|
||||
File.WriteAllBytes(path, kvp.Value);
|
||||
}
|
||||
});
|
||||
|
||||
Log.Information("{FileName} successfully exported", fileName);
|
||||
FLogger.AppendInformation();
|
||||
FLogger.AppendText($"Successfully exported '{fileName}'", Constants.WHITE, true);
|
||||
if (updateUi)
|
||||
{
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
{
|
||||
FLogger.Text("Successfully exported ", Constants.WHITE);
|
||||
FLogger.Link(fileName, path, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error("{FileName} could not be exported", fileName);
|
||||
FLogger.AppendError();
|
||||
FLogger.AppendText($"Could not export '{fileName}'", Constants.WHITE, true);
|
||||
if (updateUi)
|
||||
FLogger.Append(ELog.Error, () => FLogger.Text($"Could not export '{fileName}'", Constants.WHITE, true));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -41,8 +41,8 @@ public class ImageCommand : ViewModelCommand<TabItem>
|
|||
ClipboardExtensions.SetImage(contextViewModel.SelectedImage.ImageBuffer, $"{contextViewModel.SelectedImage.ExportName}.png");
|
||||
break;
|
||||
case "Save":
|
||||
contextViewModel.SaveImage(false);
|
||||
contextViewModel.SaveImage();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using AdonisUI.Controls;
|
||||
using CUE4Parse.UE4.Readers;
|
||||
using CUE4Parse.UE4.Vfs;
|
||||
using CUE4Parse.UE4.VirtualFileSystem;
|
||||
using FModel.Creator;
|
||||
using FModel.Extensions;
|
||||
using FModel.Framework;
|
||||
using FModel.Services;
|
||||
|
|
@ -32,73 +33,66 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
|||
private ApplicationViewModel _applicationView => ApplicationService.ApplicationView;
|
||||
private DiscordHandler _discordHandler => DiscordService.DiscordHandler;
|
||||
|
||||
public LoadCommand(LoadingModesViewModel contextViewModel) : base(contextViewModel)
|
||||
{
|
||||
}
|
||||
public LoadCommand(LoadingModesViewModel contextViewModel) : base(contextViewModel) { }
|
||||
|
||||
public override async void Execute(LoadingModesViewModel contextViewModel, object parameter)
|
||||
{
|
||||
if (_applicationView.CUE4Parse.GameDirectory.HasNoFile) return;
|
||||
if (_applicationView.CUE4Parse.Provider.Files.Count <= 0)
|
||||
{
|
||||
FLogger.AppendError();
|
||||
FLogger.AppendText("An encrypted archive has been found. In order to decrypt it, please specify a working AES encryption key", Constants.WHITE, true);
|
||||
FLogger.Append(ELog.Error, () =>
|
||||
FLogger.Text("An encrypted archive has been found. In order to decrypt it, please specify a working AES encryption key", Constants.WHITE, true));
|
||||
return;
|
||||
}
|
||||
|
||||
if (_applicationView.CUE4Parse.Game == FGame.FortniteGame &&
|
||||
_applicationView.CUE4Parse.Provider.MappingsContainer == null)
|
||||
{
|
||||
FLogger.AppendError();
|
||||
FLogger.AppendText("Mappings could not get pulled, extracting packages might not work properly. If so, either press F12, restart, or come back later.", Constants.WHITE, true);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
var loadingTime = Stopwatch.StartNew();
|
||||
#endif
|
||||
_applicationView.CUE4Parse.AssetsFolder.Folders.Clear();
|
||||
_applicationView.CUE4Parse.SearchVm.SearchResults.Clear();
|
||||
MainWindow.YesWeCats.LeftTabControl.SelectedIndex = 1; // folders tab
|
||||
|
||||
await _applicationView.CUE4Parse.LoadLocalizedResources(); // load locres if not already loaded
|
||||
await _applicationView.CUE4Parse.LoadVirtualPaths(); // load virtual paths if not already loaded
|
||||
Helper.CloseWindow<AdonisWindow>("Search View"); // close search window if opened
|
||||
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
// filter what to show
|
||||
switch (UserSettings.Default.LoadingMode)
|
||||
await Task.WhenAll(
|
||||
_applicationView.CUE4Parse.LoadLocalizedResources(), // load locres if not already loaded,
|
||||
_applicationView.CUE4Parse.LoadVirtualPaths(), // load virtual paths if not already loaded
|
||||
Task.Run(() => Utils.Typefaces = new Typefaces(_applicationView.CUE4Parse)),
|
||||
_threadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
case ELoadingMode.Single:
|
||||
case ELoadingMode.Multiple:
|
||||
// filter what to show
|
||||
switch (UserSettings.Default.LoadingMode)
|
||||
{
|
||||
var l = (IList) parameter;
|
||||
if (l.Count < 1) return;
|
||||
case ELoadingMode.Single:
|
||||
case ELoadingMode.Multiple:
|
||||
{
|
||||
var l = (IList) parameter;
|
||||
if (l.Count < 1) return;
|
||||
|
||||
var directoryFilesToShow = l.Cast<FileItem>();
|
||||
FilterDirectoryFilesToDisplay(cancellationToken, directoryFilesToShow);
|
||||
break;
|
||||
var directoryFilesToShow = l.Cast<FileItem>();
|
||||
FilterDirectoryFilesToDisplay(cancellationToken, directoryFilesToShow);
|
||||
break;
|
||||
}
|
||||
case ELoadingMode.All:
|
||||
{
|
||||
FilterDirectoryFilesToDisplay(cancellationToken, null);
|
||||
break;
|
||||
}
|
||||
case ELoadingMode.AllButNew:
|
||||
case ELoadingMode.AllButModified:
|
||||
{
|
||||
FilterNewOrModifiedFilesToDisplay(cancellationToken);
|
||||
break;
|
||||
}
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
case ELoadingMode.All:
|
||||
{
|
||||
FilterDirectoryFilesToDisplay(cancellationToken, null);
|
||||
break;
|
||||
}
|
||||
case ELoadingMode.AllButNew:
|
||||
case ELoadingMode.AllButModified:
|
||||
{
|
||||
FilterNewOrModifiedFilesToDisplay(cancellationToken);
|
||||
break;
|
||||
}
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
_discordHandler.UpdatePresence(_applicationView.CUE4Parse);
|
||||
});
|
||||
_discordHandler.UpdatePresence(_applicationView.CUE4Parse);
|
||||
})
|
||||
).ConfigureAwait(false);
|
||||
#if DEBUG
|
||||
loadingTime.Stop();
|
||||
FLogger.AppendDebug();
|
||||
FLogger.AppendText($"{_applicationView.CUE4Parse.SearchVm.SearchResults.Count} packages, {_applicationView.CUE4Parse.LocalizedResourcesCount} localized resources, and {_applicationView.CUE4Parse.VirtualPathCount} virtual paths loaded in {loadingTime.Elapsed.TotalSeconds.ToString("F3", CultureInfo.InvariantCulture)} seconds", Constants.WHITE, true);
|
||||
FLogger.Append(ELog.Debug, () =>
|
||||
FLogger.Text($"{_applicationView.CUE4Parse.SearchVm.SearchResults.Count} packages loaded in {loadingTime.Elapsed.TotalSeconds.ToString("F3", CultureInfo.InvariantCulture)} seconds", Constants.WHITE, true));
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
@ -159,8 +153,8 @@ public class LoadCommand : ViewModelCommand<LoadingModesViewModel>
|
|||
|
||||
if (!openFileDialog.ShowDialog().GetValueOrDefault()) return;
|
||||
|
||||
FLogger.AppendInformation();
|
||||
FLogger.AppendText($"Backup file older than current game is '{openFileDialog.FileName.SubstringAfterLast("\\")}'", Constants.WHITE, true);
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
FLogger.Text($"Backup file older than current game is '{openFileDialog.FileName.SubstringAfterLast("\\")}'", Constants.WHITE, true));
|
||||
|
||||
using var fileStream = new FileStream(openFileDialog.FileName, FileMode.Open);
|
||||
using var memoryStream = new MemoryStream();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Diagnostics;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using AdonisUI.Controls;
|
||||
using FModel.Extensions;
|
||||
|
|
@ -33,7 +34,7 @@ public class MenuCommand : ViewModelCommand<ApplicationViewModel>
|
|||
case "Directory_ArchivesInfo":
|
||||
contextViewModel.CUE4Parse.TabControl.AddTab("Archives Info");
|
||||
contextViewModel.CUE4Parse.TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector("json");
|
||||
contextViewModel.CUE4Parse.TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(contextViewModel.CUE4Parse.GameDirectory.DirectoryFiles, Formatting.Indented), false);
|
||||
contextViewModel.CUE4Parse.TabControl.SelectedTab.SetDocumentText(JsonConvert.SerializeObject(contextViewModel.CUE4Parse.GameDirectory.DirectoryFiles, Formatting.Indented), false, false);
|
||||
break;
|
||||
case "Views_3dViewer":
|
||||
contextViewModel.CUE4Parse.SnooperViewer.Run();
|
||||
|
|
@ -76,22 +77,16 @@ public class MenuCommand : ViewModelCommand<ApplicationViewModel>
|
|||
case "ToolBox_Open_Output_Directory":
|
||||
Process.Start(new ProcessStartInfo { FileName = UserSettings.Default.OutputDirectory, UseShellExecute = true });
|
||||
break;
|
||||
case "ToolBox_Expand_All":
|
||||
await ApplicationService.ThreadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
foreach (var folder in contextViewModel.CUE4Parse.AssetsFolder.Folders)
|
||||
{
|
||||
LoopFolders(cancellationToken, folder, true);
|
||||
}
|
||||
});
|
||||
break;
|
||||
// case "ToolBox_Expand_All":
|
||||
// await ApplicationService.ThreadWorkerView.Begin(cancellationToken =>
|
||||
// {
|
||||
// SetFoldersIsExpanded(contextViewModel.CUE4Parse.AssetsFolder, true, cancellationToken);
|
||||
// });
|
||||
// break;
|
||||
case "ToolBox_Collapse_All":
|
||||
await ApplicationService.ThreadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
foreach (var folder in contextViewModel.CUE4Parse.AssetsFolder.Folders)
|
||||
{
|
||||
LoopFolders(cancellationToken, folder, false);
|
||||
}
|
||||
SetFoldersIsExpanded(contextViewModel.CUE4Parse.AssetsFolder, false, cancellationToken);
|
||||
});
|
||||
break;
|
||||
case TreeItem selectedFolder:
|
||||
|
|
@ -101,15 +96,41 @@ public class MenuCommand : ViewModelCommand<ApplicationViewModel>
|
|||
}
|
||||
}
|
||||
|
||||
private void LoopFolders(CancellationToken cancellationToken, TreeItem parent, bool isExpanded)
|
||||
private void SetFoldersIsExpanded(AssetsFolderViewModel root, bool expand, CancellationToken cancellationToken)
|
||||
{
|
||||
if (parent.IsExpanded != isExpanded)
|
||||
var nodes = new LinkedList<TreeItem>();
|
||||
foreach (TreeItem folder in root.Folders)
|
||||
nodes.AddLast(folder);
|
||||
|
||||
var current = nodes.First;
|
||||
while (current != null)
|
||||
{
|
||||
parent.IsExpanded = isExpanded;
|
||||
Thread.Sleep(10);
|
||||
var folder = current.Value;
|
||||
|
||||
// Collapse top-down (reduce layout updates)
|
||||
if (!expand && folder.IsExpanded)
|
||||
{
|
||||
folder.IsExpanded = false;
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
foreach (var child in folder.Folders)
|
||||
{
|
||||
nodes.AddLast(child);
|
||||
}
|
||||
|
||||
current = current.Next;
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
foreach (var f in parent.Folders) LoopFolders(cancellationToken, f, isExpanded);
|
||||
if (!expand) return;
|
||||
|
||||
// Expand bottom-up (reduce layout updates)
|
||||
for (var node = nodes.Last; node != null; node = node.Previous)
|
||||
{
|
||||
node.Value.IsExpanded = true;
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Collections;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using FModel.Framework;
|
||||
using FModel.Services;
|
||||
|
||||
|
|
@ -28,6 +29,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
|
|||
case "Assets_Extract_New_Tab":
|
||||
foreach (var asset in assetItems)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, true);
|
||||
}
|
||||
|
|
@ -35,6 +37,7 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
|
|||
case "Assets_Export_Data":
|
||||
foreach (var asset in assetItems)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.ExportData(asset.FullPath);
|
||||
}
|
||||
|
|
@ -42,31 +45,33 @@ public class RightClickMenuCommand : ViewModelCommand<ApplicationViewModel>
|
|||
case "Assets_Save_Properties":
|
||||
foreach (var asset in assetItems)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Properties);
|
||||
contextViewModel.CUE4Parse.TabControl.SelectedTab.SaveProperty(false);
|
||||
}
|
||||
break;
|
||||
case "Assets_Save_Textures":
|
||||
foreach (var asset in assetItems)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Textures);
|
||||
contextViewModel.CUE4Parse.TabControl.SelectedTab.SaveImages(false);
|
||||
}
|
||||
break;
|
||||
case "Assets_Save_Models":
|
||||
foreach (var asset in assetItems)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Meshes);
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Meshes | EBulkType.Auto);
|
||||
}
|
||||
break;
|
||||
case "Assets_Save_Animations":
|
||||
foreach (var asset in assetItems)
|
||||
{
|
||||
Thread.Yield();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Animations);
|
||||
contextViewModel.CUE4Parse.Extract(cancellationToken, asset.FullPath, false, EBulkType.Animations | EBulkType.Auto);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,26 +38,24 @@ public class TabCommand : ViewModelCommand<TabItem>
|
|||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
_applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.FullPath, false, EBulkType.Properties);
|
||||
_applicationView.CUE4Parse.TabControl.SelectedTab.SaveProperty(false);
|
||||
});
|
||||
break;
|
||||
case "Asset_Save_Textures":
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
_applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.FullPath, false, EBulkType.Textures);
|
||||
_applicationView.CUE4Parse.TabControl.SelectedTab.SaveImages(false);
|
||||
});
|
||||
break;
|
||||
case "Asset_Save_Models":
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
_applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.FullPath, false, EBulkType.Meshes);
|
||||
_applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.FullPath, false, EBulkType.Meshes | EBulkType.Auto);
|
||||
});
|
||||
break;
|
||||
case "Asset_Save_Animations":
|
||||
await _threadWorkerView.Begin(cancellationToken =>
|
||||
{
|
||||
_applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.FullPath, false, EBulkType.Animations);
|
||||
_applicationView.CUE4Parse.Extract(cancellationToken, contextViewModel.FullPath, false, EBulkType.Animations | EBulkType.Auto);
|
||||
});
|
||||
break;
|
||||
case "Open_Properties":
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
using FModel.Framework;
|
||||
using FModel.Framework;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using CUE4Parse.UE4.Vfs;
|
||||
using CUE4Parse.UE4.VirtualFileSystem;
|
||||
|
||||
namespace FModel.ViewModels;
|
||||
|
||||
|
|
@ -111,4 +111,4 @@ public class GameDirectoryViewModel : ViewModel
|
|||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ public class GameSelectorViewModel : ViewModel
|
|||
public EGame OverridedGame { get; set; }
|
||||
public List<FCustomVersion> OverridedCustomVersions { get; set; }
|
||||
public Dictionary<string, bool> OverridedOptions { get; set; }
|
||||
public Dictionary<string, KeyValuePair<string, string>> OverridedMapStructTypes { get; set; }
|
||||
public IList<CustomDirectory> CustomDirectories { get; set; }
|
||||
}
|
||||
|
||||
|
|
@ -74,6 +75,7 @@ public class GameSelectorViewModel : ViewModel
|
|||
OverridedGame = EGame.GAME_UE4_LATEST,
|
||||
OverridedCustomVersions = null,
|
||||
OverridedOptions = null,
|
||||
OverridedMapStructTypes = null,
|
||||
CustomDirectories = new List<CustomDirectory>()
|
||||
};
|
||||
|
||||
|
|
@ -116,6 +118,7 @@ public class GameSelectorViewModel : ViewModel
|
|||
yield return GetMojangGame("MinecraftDungeons", "\\dungeons\\dungeons\\Dungeons\\Content\\Paks");
|
||||
yield return GetSteamGame(381210, "\\DeadByDaylight\\Content\\Paks"); // Dead By Daylight
|
||||
yield return GetSteamGame(578080, "\\TslGame\\Content\\Paks"); // PUBG
|
||||
yield return GetSteamGame(1172380, "\\SwGame\\Content\\Paks"); // STAR WARS Jedi: Fallen Order™
|
||||
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
|
||||
|
|
|
|||
|
|
@ -76,6 +76,13 @@ public class SettingsViewModel : ViewModel
|
|||
set => SetProperty(ref _selectedOptions, value);
|
||||
}
|
||||
|
||||
private Dictionary<string, KeyValuePair<string, string>> _selectedMapStructTypes;
|
||||
public Dictionary<string, KeyValuePair<string, string>> SelectedMapStructTypes
|
||||
{
|
||||
get => _selectedMapStructTypes;
|
||||
set => SetProperty(ref _selectedMapStructTypes, value);
|
||||
}
|
||||
|
||||
private FEndpoint _aesEndpoint;
|
||||
public FEndpoint AesEndpoint
|
||||
{
|
||||
|
|
@ -192,6 +199,7 @@ public class SettingsViewModel : ViewModel
|
|||
private EGame _ueGameSnapshot;
|
||||
private List<FCustomVersion> _customVersionsSnapshot;
|
||||
private Dictionary<string, bool> _optionsSnapshot;
|
||||
private Dictionary<string, KeyValuePair<string, string>> _mapStructTypesSnapshot;
|
||||
private ELanguage _assetLanguageSnapshot;
|
||||
private ECompressedAudio _compressedAudioSnapshot;
|
||||
private EIconStyle _cosmeticStyleSnapshot;
|
||||
|
|
@ -225,12 +233,14 @@ public class SettingsViewModel : ViewModel
|
|||
_ueGameSnapshot = settings.OverridedGame;
|
||||
_customVersionsSnapshot = settings.OverridedCustomVersions;
|
||||
_optionsSnapshot = settings.OverridedOptions;
|
||||
_mapStructTypesSnapshot = settings.OverridedMapStructTypes;
|
||||
}
|
||||
else
|
||||
{
|
||||
_ueGameSnapshot = UserSettings.Default.OverridedGame[_game];
|
||||
_customVersionsSnapshot = UserSettings.Default.OverridedCustomVersions[_game];
|
||||
_optionsSnapshot = UserSettings.Default.OverridedOptions[_game];
|
||||
_mapStructTypesSnapshot = UserSettings.Default.OverridedMapStructTypes[_game];
|
||||
}
|
||||
|
||||
if (UserSettings.Default.CustomEndpoints.TryGetValue(_game, out var endpoints))
|
||||
|
|
@ -259,6 +269,7 @@ public class SettingsViewModel : ViewModel
|
|||
SelectedUeGame = _ueGameSnapshot;
|
||||
SelectedCustomVersions = _customVersionsSnapshot;
|
||||
SelectedOptions = _optionsSnapshot;
|
||||
SelectedMapStructTypes = _mapStructTypesSnapshot;
|
||||
SelectedAssetLanguage = _assetLanguageSnapshot;
|
||||
SelectedCompressedAudio = _compressedAudioSnapshot;
|
||||
SelectedCosmeticStyle = _cosmeticStyleSnapshot;
|
||||
|
|
@ -317,6 +328,12 @@ public class SettingsViewModel : ViewModel
|
|||
{
|
||||
SelectedOptions[k] = v;
|
||||
}
|
||||
|
||||
SelectedMapStructTypes = new Dictionary<string, KeyValuePair<string, string>>();
|
||||
foreach (var (k, v) in version.MapStructTypes)
|
||||
{
|
||||
SelectedMapStructTypes[k] = v;
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetPreset()
|
||||
|
|
@ -324,6 +341,7 @@ public class SettingsViewModel : ViewModel
|
|||
SelectedUeGame = _ueGameSnapshot;
|
||||
SelectedCustomVersions = _customVersionsSnapshot;
|
||||
SelectedOptions = _optionsSnapshot;
|
||||
SelectedMapStructTypes = _mapStructTypesSnapshot;
|
||||
}
|
||||
|
||||
public bool Save(out List<SettingsOut> whatShouldIDo)
|
||||
|
|
@ -340,6 +358,7 @@ public class SettingsViewModel : ViewModel
|
|||
|
||||
if (_ueGameSnapshot != SelectedUeGame || _customVersionsSnapshot != SelectedCustomVersions ||
|
||||
_uePlatformSnapshot != SelectedUePlatform || _optionsSnapshot != SelectedOptions || // combobox
|
||||
_mapStructTypesSnapshot != SelectedMapStructTypes ||
|
||||
_outputSnapshot != UserSettings.Default.OutputDirectory || // textbox
|
||||
_rawDataSnapshot != UserSettings.Default.RawDataDirectory || // textbox
|
||||
_propertiesSnapshot != UserSettings.Default.PropertiesDirectory || // textbox
|
||||
|
|
@ -357,12 +376,14 @@ public class SettingsViewModel : ViewModel
|
|||
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedGame = SelectedUeGame;
|
||||
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedCustomVersions = SelectedCustomVersions;
|
||||
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedOptions = SelectedOptions;
|
||||
UserSettings.Default.ManualGames[UserSettings.Default.GameDirectory].OverridedMapStructTypes = SelectedMapStructTypes;
|
||||
}
|
||||
else
|
||||
{
|
||||
UserSettings.Default.OverridedGame[_game] = SelectedUeGame;
|
||||
UserSettings.Default.OverridedCustomVersions[_game] = SelectedCustomVersions;
|
||||
UserSettings.Default.OverridedOptions[_game] = SelectedOptions;
|
||||
UserSettings.Default.OverridedMapStructTypes[_game] = SelectedMapStructTypes;
|
||||
}
|
||||
|
||||
UserSettings.Default.AssetLanguage = SelectedAssetLanguage;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ using FModel.ViewModels.Commands;
|
|||
using FModel.Views.Resources.Controls;
|
||||
using ICSharpCode.AvalonEdit.Document;
|
||||
using ICSharpCode.AvalonEdit.Highlighting;
|
||||
using Microsoft.Win32;
|
||||
using Serilog;
|
||||
using SkiaSharp;
|
||||
using System.Collections.Generic;
|
||||
|
|
@ -17,7 +16,6 @@ using System.Windows;
|
|||
using System.Windows.Media.Imaging;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse_Conversion.Textures;
|
||||
using Ookii.Dialogs.Wpf;
|
||||
|
||||
namespace FModel.ViewModels;
|
||||
|
||||
|
|
@ -230,21 +228,21 @@ public class TabItem : ViewModel
|
|||
});
|
||||
}
|
||||
|
||||
public void AddImage(UTexture2D texture, bool bulkTexture)
|
||||
=> AddImage(texture.Name, texture.bRenderNearestNeighbor, texture.Decode(UserSettings.Default.OverridedPlatform), bulkTexture);
|
||||
public void AddImage(UTexture2D texture, bool save, bool updateUi)
|
||||
=> AddImage(texture.Name, texture.bRenderNearestNeighbor, texture.Decode(UserSettings.Default.OverridedPlatform), save, updateUi);
|
||||
|
||||
public void AddImage(string name, bool rnn, SKBitmap[] img, bool bulkTexture)
|
||||
public void AddImage(string name, bool rnn, SKBitmap[] img, bool save, bool updateUi)
|
||||
{
|
||||
foreach (var i in img) AddImage(name, rnn, i, bulkTexture);
|
||||
foreach (var i in img) AddImage(name, rnn, i, save, updateUi);
|
||||
}
|
||||
|
||||
public void AddImage(string name, bool rnn, SKBitmap img, bool bulkTexture)
|
||||
public void AddImage(string name, bool rnn, SKBitmap img, bool save, bool updateUi)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
var t = new TabImage(name, rnn, img);
|
||||
if (bulkTexture)
|
||||
SaveImage(t, true);
|
||||
if (save) SaveImage(t, updateUi);
|
||||
if (!updateUi) return;
|
||||
|
||||
_images.Add(t);
|
||||
SelectedImage ??= t;
|
||||
|
|
@ -256,15 +254,15 @@ public class TabItem : ViewModel
|
|||
public void GoPreviousImage() => SelectedImage = _images.Previous(SelectedImage);
|
||||
public void GoNextImage() => SelectedImage = _images.Next(SelectedImage);
|
||||
|
||||
public void SetDocumentText(string text, bool bulkSave)
|
||||
public void SetDocumentText(string text, bool save, bool updateUi)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
Document ??= new TextDocument();
|
||||
Document.Text = text;
|
||||
Document.UndoStack.ClearAll();
|
||||
|
||||
if (bulkSave)
|
||||
SaveProperty(true);
|
||||
if (save) SaveProperty(updateUi);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -277,66 +275,23 @@ public class TabItem : ViewModel
|
|||
});
|
||||
}
|
||||
|
||||
public void SaveImages(bool bulkTexture)
|
||||
{
|
||||
switch (_images.Count)
|
||||
{
|
||||
case 1:
|
||||
SaveImage(bulkTexture);
|
||||
break;
|
||||
case > 1:
|
||||
var directory = Path.Combine(UserSettings.Default.TextureDirectory,
|
||||
UserSettings.Default.KeepDirectoryStructure ? Directory : "").Replace('\\', '/');
|
||||
|
||||
if (!bulkTexture)
|
||||
{
|
||||
var folderBrowser = new VistaFolderBrowserDialog();
|
||||
if (folderBrowser.ShowDialog() == true)
|
||||
directory = folderBrowser.SelectedPath;
|
||||
else return;
|
||||
}
|
||||
else System.IO.Directory.CreateDirectory(directory);
|
||||
|
||||
foreach (var image in _images)
|
||||
{
|
||||
if (image == null) return;
|
||||
var fileName = $"{image.ExportName}.png";
|
||||
SaveImage(image, Path.Combine(directory, fileName), fileName);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveImage(bool bulkTexture) => SaveImage(SelectedImage, bulkTexture);
|
||||
private void SaveImage(TabImage image, bool bulkTexture)
|
||||
public void SaveImage() => SaveImage(SelectedImage, true);
|
||||
private void SaveImage(TabImage image, bool updateUi)
|
||||
{
|
||||
if (image == null) return;
|
||||
var fileName = $"{image.ExportName}.png";
|
||||
var path = Path.Combine(UserSettings.Default.TextureDirectory,
|
||||
UserSettings.Default.KeepDirectoryStructure ? Directory : "", fileName!).Replace('\\', '/');
|
||||
|
||||
if (!bulkTexture)
|
||||
{
|
||||
var saveFileDialog = new SaveFileDialog
|
||||
{
|
||||
Title = "Save Texture",
|
||||
FileName = fileName,
|
||||
InitialDirectory = UserSettings.Default.TextureDirectory,
|
||||
Filter = "PNG Files (*.png)|*.png|All Files (*.*)|*.*"
|
||||
};
|
||||
var result = saveFileDialog.ShowDialog();
|
||||
if (!result.HasValue || !result.Value) return;
|
||||
path = saveFileDialog.FileName;
|
||||
}
|
||||
else System.IO.Directory.CreateDirectory(path.SubstringBeforeLast('/'));
|
||||
System.IO.Directory.CreateDirectory(path.SubstringBeforeLast('/'));
|
||||
|
||||
SaveImage(image, path, fileName);
|
||||
SaveImage(image, path, fileName, updateUi);
|
||||
}
|
||||
|
||||
private void SaveImage(TabImage image, string path, string fileName)
|
||||
private void SaveImage(TabImage image, string path, string fileName, bool updateUi)
|
||||
{
|
||||
SaveImage(image, path);
|
||||
SaveCheck(path, fileName);
|
||||
SaveCheck(path, fileName, updateUi);
|
||||
}
|
||||
|
||||
private void SaveImage(TabImage image, string path)
|
||||
|
|
@ -345,47 +300,37 @@ public class TabItem : ViewModel
|
|||
fs.Write(image.ImageBuffer, 0, image.ImageBuffer.Length);
|
||||
}
|
||||
|
||||
public void SaveProperty(bool autoSave)
|
||||
public void SaveProperty(bool updateUi)
|
||||
{
|
||||
var fileName = Path.ChangeExtension(Header, ".json");
|
||||
var directory = Path.Combine(UserSettings.Default.PropertiesDirectory,
|
||||
UserSettings.Default.KeepDirectoryStructure ? Directory : "", fileName).Replace('\\', '/');
|
||||
|
||||
if (!autoSave)
|
||||
{
|
||||
var saveFileDialog = new SaveFileDialog
|
||||
{
|
||||
Title = "Save Property",
|
||||
FileName = fileName,
|
||||
InitialDirectory = UserSettings.Default.PropertiesDirectory,
|
||||
Filter = "JSON Files (*.json)|*.json|INI Files (*.ini)|*.ini|XML Files (*.xml)|*.xml|All Files (*.*)|*.*"
|
||||
};
|
||||
var result = saveFileDialog.ShowDialog();
|
||||
if (!result.HasValue || !result.Value) return;
|
||||
directory = saveFileDialog.FileName;
|
||||
}
|
||||
else
|
||||
{
|
||||
System.IO.Directory.CreateDirectory(directory.SubstringBeforeLast('/'));
|
||||
}
|
||||
System.IO.Directory.CreateDirectory(directory.SubstringBeforeLast('/'));
|
||||
|
||||
Application.Current.Dispatcher.Invoke(() => File.WriteAllText(directory, Document.Text));
|
||||
SaveCheck(directory, fileName);
|
||||
SaveCheck(directory, fileName, updateUi);
|
||||
}
|
||||
|
||||
private void SaveCheck(string path, string fileName)
|
||||
private void SaveCheck(string path, string fileName, bool updateUi)
|
||||
{
|
||||
if (File.Exists(path))
|
||||
{
|
||||
Log.Information("{FileName} successfully saved", fileName);
|
||||
FLogger.AppendInformation();
|
||||
FLogger.AppendText($"Successfully saved '{fileName}'", Constants.WHITE, true);
|
||||
if (updateUi)
|
||||
{
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
{
|
||||
FLogger.Text("Successfully saved ", Constants.WHITE);
|
||||
FLogger.Link(fileName, path, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error("{FileName} could not be saved", fileName);
|
||||
FLogger.AppendError();
|
||||
FLogger.AppendText($"Could not save '{fileName}'", Constants.WHITE, true);
|
||||
if (updateUi)
|
||||
FLogger.Append(ELog.Error, () => FLogger.Text($"Could not save '{fileName}'", Constants.WHITE, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,7 +52,9 @@ public class ThreadWorkerViewModel : ViewModel
|
|||
|
||||
public async Task Begin(Action<CancellationToken> action)
|
||||
{
|
||||
if (!_applicationView.Status.IsReady)
|
||||
if (_applicationView.CUE4Parse.IsSnooperOpen)
|
||||
_applicationView.CUE4Parse.SnooperViewer.Close();
|
||||
else if (!_applicationView.Status.IsReady)
|
||||
{
|
||||
SignalOperationInProgress();
|
||||
return;
|
||||
|
|
@ -89,6 +91,8 @@ public class ThreadWorkerViewModel : ViewModel
|
|||
catch (OperationCanceledException)
|
||||
{
|
||||
_applicationView.Status.SetStatus(EStatusKind.Stopped);
|
||||
if (_applicationView.CUE4Parse.IsSnooperOpen)
|
||||
_applicationView.CUE4Parse.SnooperViewer.Close();
|
||||
CurrentCancellationTokenSource = null; // kill token
|
||||
OperationCancelled = true;
|
||||
OperationCancelled = false;
|
||||
|
|
@ -101,34 +105,36 @@ public class ThreadWorkerViewModel : ViewModel
|
|||
|
||||
Log.Error("{Exception}", e);
|
||||
|
||||
FLogger.AppendError();
|
||||
if ((e.InnerException ?? e) is { TargetSite.DeclaringType: not null } exception)
|
||||
FLogger.Append(ELog.Error, () =>
|
||||
{
|
||||
if (exception.TargetSite.ToString() == "CUE4Parse.FileProvider.GameFile get_Item(System.String)")
|
||||
if ((e.InnerException ?? e) is { TargetSite.DeclaringType: not null } exception)
|
||||
{
|
||||
FLogger.AppendText(e.Message, Constants.WHITE, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
var t = exception.GetType();
|
||||
FLogger.AppendText(t.Namespace + _dot, Constants.GRAY);
|
||||
FLogger.AppendText(t.Name, Constants.WHITE);
|
||||
FLogger.AppendText(_colon + " ", Constants.GRAY);
|
||||
FLogger.AppendText(exception.Message, Constants.RED, true);
|
||||
|
||||
FLogger.AppendText(_at, _gray);
|
||||
FLogger.AppendText(exception.TargetSite.DeclaringType.FullName + _dot, Constants.GRAY);
|
||||
FLogger.AppendText(exception.TargetSite.Name, Constants.YELLOW);
|
||||
|
||||
var p = exception.TargetSite.GetParameters();
|
||||
var parameters = new string[p.Length];
|
||||
for (int i = 0; i < parameters.Length; i++)
|
||||
if (exception.TargetSite.ToString() == "CUE4Parse.FileProvider.GameFile get_Item(System.String)")
|
||||
{
|
||||
parameters[i] = p[i].ParameterType.Name + " " + p[i].Name;
|
||||
FLogger.Text(e.Message, Constants.WHITE, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
var t = exception.GetType();
|
||||
FLogger.Text(t.Namespace + _dot, Constants.GRAY);
|
||||
FLogger.Text(t.Name, Constants.WHITE);
|
||||
FLogger.Text(_colon + " ", Constants.GRAY);
|
||||
FLogger.Text(exception.Message, Constants.RED, true);
|
||||
|
||||
FLogger.Text(_at, _gray);
|
||||
FLogger.Text(exception.TargetSite.DeclaringType.FullName + _dot, Constants.GRAY);
|
||||
FLogger.Text(exception.TargetSite.Name, Constants.YELLOW);
|
||||
|
||||
var p = exception.TargetSite.GetParameters();
|
||||
var parameters = new string[p.Length];
|
||||
for (int i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
parameters[i] = p[i].ParameterType.Name + " " + p[i].Name;
|
||||
}
|
||||
FLogger.Text("(" + string.Join(", ", parameters) + ")", Constants.GRAY, true);
|
||||
}
|
||||
FLogger.AppendText("(" + string.Join(", ", parameters) + ")", Constants.GRAY, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,6 @@ public partial class AesManager
|
|||
|
||||
private async void OnClosing(object sender, CancelEventArgs e)
|
||||
{
|
||||
await _applicationView.AesManager.UpdateProvider(false);
|
||||
await _applicationView.UpdateProvider(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
|
||||
<ListBox x:Name="ImagesListBox" Grid.Row="0" SelectionMode="Extended" />
|
||||
<Separator Grid.Row="1" Style="{StaticResource CustomSeparator}" Tag="COMMANDS" />
|
||||
<Grid Grid.Row="2" Margin="0 0 0 10">
|
||||
|
|
@ -51,10 +51,10 @@
|
|||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="Images Per Row" VerticalAlignment="Center" Margin="0 0 10 0" />
|
||||
<Slider x:Name="SizeSlider" Grid.Row="0" Grid.Column="1" TickPlacement="None" AutoToolTipPlacement="BottomRight"
|
||||
IsMoveToPointEnabled="True" Minimum="2" Maximum="20" TickFrequency="1" MouseUp="Click_DrawPreview" Thumb.DragCompleted="DrawPreview"/>
|
||||
IsMoveToPointEnabled="True" Minimum="1" Maximum="20" TickFrequency="1" MouseUp="Click_DrawPreview" Thumb.DragCompleted="DrawPreview"/>
|
||||
<TextBlock Grid.Row="2" Grid.Column="0" Text="Margin Between Images" VerticalAlignment="Center" Margin="0 0 10 0" />
|
||||
<Slider Grid.Row="2" Grid.Column="1" Value="{Binding ImageMergerMargin, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
|
||||
TickPlacement="None" AutoToolTipPlacement="BottomRight" IsMoveToPointEnabled="True" Minimum="0" Maximum="50" TickFrequency="1"
|
||||
|
|
@ -106,4 +106,4 @@
|
|||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</adonisControls:AdonisWindow>
|
||||
</adonisControls:AdonisWindow>
|
||||
|
|
|
|||
|
|
@ -84,6 +84,8 @@ public partial class ImageMerger
|
|||
{
|
||||
maxWidth = curW + image.Width + margin;
|
||||
curH += lineMaxHeight + margin;
|
||||
if (curH > maxHeight)
|
||||
maxHeight = curH;
|
||||
|
||||
curW = 0;
|
||||
lineMaxHeight = 0;
|
||||
|
|
@ -279,14 +281,16 @@ public partial class ImageMerger
|
|||
if (File.Exists(path))
|
||||
{
|
||||
Log.Information("{FileName} successfully saved", fileName);
|
||||
FLogger.AppendInformation();
|
||||
FLogger.AppendText($"Successfully saved '{fileName}'", Constants.WHITE, true);
|
||||
FLogger.Append(ELog.Information, () =>
|
||||
{
|
||||
FLogger.Text("Successfully saved ", Constants.WHITE);
|
||||
FLogger.Link(fileName, path, true);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error("{FileName} could not be saved", fileName);
|
||||
FLogger.AppendError();
|
||||
FLogger.AppendText($"Could not save '{fileName}'", Constants.WHITE, true);
|
||||
FLogger.Append(ELog.Error, () => FLogger.Text($"Could not save '{fileName}'", Constants.WHITE, true));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -294,4 +298,4 @@ public partial class ImageMerger
|
|||
{
|
||||
ClipboardExtensions.SetImage(_imageBuffer, FILENAME);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,14 +51,12 @@ public partial class MapViewer
|
|||
if (File.Exists(path))
|
||||
{
|
||||
Log.Information("MiniMap.png successfully saved");
|
||||
FLogger.AppendInformation();
|
||||
FLogger.AppendText("Successfully saved 'MiniMap.png'", Constants.WHITE, true);
|
||||
FLogger.Append(ELog.Information, () => FLogger.Text("Successfully saved 'MiniMap.png'", Constants.WHITE, true));
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error("MiniMap.png could not be saved");
|
||||
FLogger.AppendError();
|
||||
FLogger.AppendText("Could not save 'MiniMap.png'", Constants.WHITE, true);
|
||||
FLogger.Append(ELog.Error, () => FLogger.Text("Could not save 'MiniMap.png'", Constants.WHITE, true));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -77,4 +75,4 @@ public partial class MapViewer
|
|||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.TextFormatting;
|
||||
using FModel.Extensions;
|
||||
using FModel.Services;
|
||||
using FModel.ViewModels;
|
||||
using ICSharpCode.AvalonEdit.Document;
|
||||
using ICSharpCode.AvalonEdit.Rendering;
|
||||
|
||||
namespace FModel.Views.Resources.Controls;
|
||||
|
|
@ -68,8 +70,20 @@ public class GamePathVisualLineText : VisualLineText
|
|||
var fullPath = _applicationView.CUE4Parse.Provider.FixPath(package, StringComparison.Ordinal);
|
||||
if (a.ParentVisualLine.Document.FileName.Equals(fullPath.SubstringBeforeLast('.'), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var lineNumber = a.ParentVisualLine.Document.Text.GetLineNumber(obj);
|
||||
var line = a.ParentVisualLine.Document.GetLineByNumber(lineNumber);
|
||||
int lineNumber;
|
||||
DocumentLine line;
|
||||
|
||||
if (Regex.IsMatch(obj, @"^(.+)\[(\d+)\]$"))
|
||||
{
|
||||
lineNumber = a.ParentVisualLine.Document.Text.GetKismetLineNumber(obj);
|
||||
line = a.ParentVisualLine.Document.GetLineByNumber(lineNumber);
|
||||
}
|
||||
else
|
||||
{
|
||||
lineNumber = a.ParentVisualLine.Document.Text.GetLineNumber(obj);
|
||||
line = a.ParentVisualLine.Document.GetLineByNumber(lineNumber);
|
||||
}
|
||||
|
||||
AvalonEditor.YesWeEditor.Select(line.Offset, line.Length);
|
||||
AvalonEditor.YesWeEditor.ScrollToLine(lineNumber);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
|
|
@ -16,15 +16,18 @@ public partial class DictionaryEditor
|
|||
private readonly bool _enableElements;
|
||||
private readonly List<FCustomVersion> _defaultCustomVersions;
|
||||
private readonly Dictionary<string, bool> _defaultOptions;
|
||||
private readonly Dictionary<string, KeyValuePair<string, string>> _defaultMapStructTypes;
|
||||
|
||||
public List<FCustomVersion> CustomVersions { get; private set; }
|
||||
public Dictionary<string, bool> Options { get; private set; }
|
||||
public Dictionary<string, KeyValuePair<string, string>> MapStructTypes { get; private set; }
|
||||
|
||||
public DictionaryEditor(string title, bool enableElements)
|
||||
{
|
||||
_enableElements = enableElements;
|
||||
_defaultCustomVersions = new List<FCustomVersion> { new() { Key = new FGuid(), Version = 0 } };
|
||||
_defaultOptions = new Dictionary<string, bool> { { "key1", true }, { "key2", false } };
|
||||
_defaultMapStructTypes = new Dictionary<string, KeyValuePair<string, string>> { { "MapName", new KeyValuePair<string, string>("KeyType", "ValueType") } };
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
|
|
@ -49,6 +52,14 @@ public partial class DictionaryEditor
|
|||
};
|
||||
}
|
||||
|
||||
public DictionaryEditor(Dictionary<string, KeyValuePair<string, string>> options, string title, bool enableElements) : this(title, enableElements)
|
||||
{
|
||||
MyAvalonEditor.Document = new TextDocument
|
||||
{
|
||||
Text = JsonConvert.SerializeObject(options ?? _defaultMapStructTypes, Formatting.Indented)
|
||||
};
|
||||
}
|
||||
|
||||
private void OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!_enableElements)
|
||||
|
|
@ -74,6 +85,12 @@ public partial class DictionaryEditor
|
|||
DialogResult = true;
|
||||
Close();
|
||||
break;
|
||||
case "MapStructTypes":
|
||||
MapStructTypes = JsonConvert.DeserializeObject<Dictionary<string, KeyValuePair<string, string>>>(MyAvalonEditor.Document.Text);
|
||||
// DialogResult = !Options.SequenceEqual(_defaultOptions);
|
||||
DialogResult = true;
|
||||
Close();
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
|
@ -100,6 +117,10 @@ public partial class DictionaryEditor
|
|||
{
|
||||
Text = JsonConvert.SerializeObject(_defaultOptions, Formatting.Indented)
|
||||
},
|
||||
"MapStructTypes" => new TextDocument
|
||||
{
|
||||
Text = JsonConvert.SerializeObject(_defaultMapStructTypes, Formatting.Indented)
|
||||
},
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace FModel.Views.Resources.Controls;
|
||||
|
|
@ -16,36 +18,89 @@ public interface ITextFormatter
|
|||
void SetText(FlowDocument document, string text);
|
||||
}
|
||||
|
||||
public enum ELog
|
||||
{
|
||||
Information,
|
||||
Warning,
|
||||
Error,
|
||||
Debug,
|
||||
None
|
||||
}
|
||||
|
||||
public class FLogger : ITextFormatter
|
||||
{
|
||||
public static CustomRichTextBox Logger;
|
||||
private static readonly BrushConverter _brushConverter = new();
|
||||
private static int _previous;
|
||||
|
||||
public static void AppendInformation() => AppendText("[INF] ", Constants.BLUE);
|
||||
public static void AppendWarning() => AppendText("[WRN] ", Constants.YELLOW);
|
||||
public static void AppendError() => AppendText("[ERR] ", Constants.RED);
|
||||
public static void AppendDebug() => AppendText("[DBG] ", Constants.GREEN);
|
||||
|
||||
public static void AppendText(string message, string color, bool newLine = false)
|
||||
public static void Append(ELog type, Action job)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(delegate
|
||||
{
|
||||
var textRange = new TextRange(Logger.Document.ContentEnd, Logger.Document.ContentEnd)
|
||||
switch (type)
|
||||
{
|
||||
Text = newLine ? $"{message}{Environment.NewLine}" : message
|
||||
};
|
||||
case ELog.Information:
|
||||
Text("[INF] ", Constants.BLUE);
|
||||
break;
|
||||
case ELog.Warning:
|
||||
Text("[WRN] ", Constants.YELLOW);
|
||||
break;
|
||||
case ELog.Error:
|
||||
Text("[ERR] ", Constants.RED);
|
||||
break;
|
||||
case ELog.Debug:
|
||||
Text("[DBG] ", Constants.GREEN);
|
||||
break;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
textRange.ApplyPropertyValue(TextElement.ForegroundProperty, _brushConverter.ConvertFromString(color));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Logger.ScrollToEnd();
|
||||
}
|
||||
job();
|
||||
});
|
||||
}
|
||||
|
||||
public static void Text(string message, string color, bool newLine = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.Document.ContentEnd.InsertTextInRun(message);
|
||||
if (newLine) Logger.Document.ContentEnd.InsertLineBreak();
|
||||
|
||||
Logger.Selection.Select(Logger.Document.ContentStart.GetPositionAtOffset(_previous), Logger.Document.ContentEnd);
|
||||
Logger.Selection.ApplyPropertyValue(TextElement.ForegroundProperty, _brushConverter.ConvertFromString(color));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Finally();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Link(string message, string url, bool newLine = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
new Hyperlink(new Run(newLine ? $"{message}{Environment.NewLine}" : message), Logger.Document.ContentEnd)
|
||||
{
|
||||
NavigateUri = new Uri(url),
|
||||
OverridesDefaultStyle = true,
|
||||
Style = new Style(typeof(Hyperlink)) { Setters =
|
||||
{
|
||||
new Setter(FrameworkContentElement.CursorProperty, Cursors.Hand),
|
||||
new Setter(TextBlock.TextDecorationsProperty, TextDecorations.Underline),
|
||||
new Setter(TextElement.ForegroundProperty, Brushes.Cornsilk)
|
||||
}}
|
||||
}.Click += (sender, _) => Process.Start("explorer.exe", $"/select, \"{((Hyperlink)sender).NavigateUri.AbsoluteUri}\"");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Finally();
|
||||
}
|
||||
}
|
||||
|
||||
private static void Finally()
|
||||
{
|
||||
Logger.ScrollToEnd();
|
||||
_previous = Math.Abs(Logger.Document.ContentEnd.GetOffsetToPosition(Logger.Document.ContentStart)) - 2;
|
||||
}
|
||||
|
||||
public string GetText(FlowDocument document)
|
||||
{
|
||||
return new TextRange(document.ContentStart, document.ContentEnd).Text;
|
||||
|
|
@ -168,4 +223,4 @@ public class CustomRichTextBox : RichTextBox
|
|||
UpdateTextFromDocument();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ public class StringToGameConverter : IValueConverter
|
|||
"MinecraftDungeons" => FGame.Dungeons,
|
||||
"shoebill" => FGame.SwGame,
|
||||
"a99769d95d8f400baad1f67ab5dfe508" => FGame.Platform,
|
||||
"711c5e95dc094ca58e5f16bd48e751d6" => FGame.PandaGame,
|
||||
"711c5e95dc094ca58e5f16bd48e751d6" => FGame.MultiVersus,
|
||||
"9361c8c6d2f34b42b5f2f61093eedf48" => FGame.TslGame,
|
||||
381210 => FGame.DeadByDaylight,
|
||||
578080 => FGame.TslGame,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
xmlns:soundOut="clr-namespace:CSCore.SoundOut;assembly=CSCore"
|
||||
xmlns:audioControls="clr-namespace:FModel.Views.Resources.Controls.Aup"
|
||||
xmlns:converters="clr-namespace:FModel.Views.Resources.Converters"
|
||||
xmlns:settings="clr-namespace:FModel.Settings"
|
||||
xmlns:avalonedit="http://icsharpcode.net/sharpdevelop/avalonedit"
|
||||
xmlns:folding="clr-namespace:ICSharpCode.AvalonEdit.Folding;assembly=ICSharpCode.AvalonEdit"
|
||||
xmlns:adonisUi="clr-namespace:AdonisUI;assembly=AdonisUI"
|
||||
|
|
@ -182,19 +183,19 @@
|
|||
</Setter.Value>
|
||||
</Setter>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static local:Settings.UserSettings.Default}}" Value="{x:Static local:ELoadingMode.Single}">
|
||||
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}}" Value="{x:Static local:ELoadingMode.Single}">
|
||||
<Setter Property="SelectionMode" Value="Single" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static local:Settings.UserSettings.Default}}" Value="{x:Static local:ELoadingMode.Multiple}">
|
||||
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}}" Value="{x:Static local:ELoadingMode.Multiple}">
|
||||
<Setter Property="SelectionMode" Value="Extended" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static local:Settings.UserSettings.Default}}" Value="{x:Static local:ELoadingMode.All}">
|
||||
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}}" Value="{x:Static local:ELoadingMode.All}">
|
||||
<Setter Property="SelectionMode" Value="Extended" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static local:Settings.UserSettings.Default}}" Value="{x:Static local:ELoadingMode.AllButNew}">
|
||||
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}}" Value="{x:Static local:ELoadingMode.AllButNew}">
|
||||
<Setter Property="SelectionMode" Value="Extended" />
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static local:Settings.UserSettings.Default}}" Value="{x:Static local:ELoadingMode.AllButModified}">
|
||||
<DataTrigger Binding="{Binding LoadingMode, Source={x:Static settings:UserSettings.Default}}" Value="{x:Static local:ELoadingMode.AllButModified}">
|
||||
<Setter Property="SelectionMode" Value="Extended" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
|
|
@ -647,7 +648,7 @@
|
|||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="{Binding AvalonImageSize, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" />
|
||||
<ColumnDefinition Width="{Binding AvalonImageSize, Source={x:Static settings:UserSettings.Default}, Mode=TwoWay}" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<controls:AvalonEditor x:Name="DynamicArea" Grid.Column="0" DataContext="{Binding SelectedItem, ElementName=TabControlName}" />
|
||||
|
|
@ -1367,6 +1368,7 @@
|
|||
<Style x:Key="CustomRichTextBox" TargetType="RichTextBox">
|
||||
<Setter Property="Height" Value="175" />
|
||||
<Setter Property="IsReadOnly" Value="True" />
|
||||
<Setter Property="IsDocumentEnabled" Value="True" />
|
||||
<Setter Property="Background" Value="{DynamicResource {x:Static adonisUi:Brushes.Layer3BackgroundBrush}}" />
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource {x:Static adonisUi:Brushes.Layer3BorderBrush}}" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}"/>
|
||||
|
|
|
|||
|
|
@ -155,6 +155,82 @@
|
|||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Header="Export Raw Data (.uasset)" Command="{Binding DataContext.RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Export_Data" />
|
||||
<Binding Path="SelectedItems" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ExportIcon}" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Save Properties (.json)" Command="{Binding DataContext.RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Properties" />
|
||||
<Binding Path="SelectedItems" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource SaveIcon}" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Save Texture (.png)" Command="{Binding DataContext.RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Textures" />
|
||||
<Binding Path="SelectedItems" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource TextureIcon}" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Save Model (.psk)" Command="{Binding DataContext.RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Models" />
|
||||
<Binding Path="SelectedItems" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource ModelIcon}" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Save Animation (.psa)" Command="{Binding DataContext.RightClickMenuCommand}">
|
||||
<MenuItem.CommandParameter>
|
||||
<MultiBinding Converter="{x:Static converters:MultiParameterConverter.Instance}">
|
||||
<Binding Source="Assets_Save_Animations" />
|
||||
<Binding Path="SelectedItems" />
|
||||
</MultiBinding>
|
||||
</MenuItem.CommandParameter>
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
<Canvas Width="24" Height="24">
|
||||
<Path Fill="{DynamicResource {x:Static adonisUi:Brushes.ForegroundBrush}}" Data="{StaticResource AnimationIcon}" />
|
||||
</Canvas>
|
||||
</Viewbox>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Header="Copy">
|
||||
<MenuItem.Icon>
|
||||
<Viewbox Width="16" Height="16">
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@
|
|||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
|
|
@ -152,10 +153,13 @@
|
|||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="5" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="5" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button Grid.Column="0" Content="Custom Versions" Click="OpenCustomVersions" />
|
||||
<Button Grid.Column="2" Content="Options" Click="OpenOptions" />
|
||||
<Button Grid.Column="4" Content="MapStructTypes" Click="OpenMapStructTypes" />
|
||||
</Grid>
|
||||
|
||||
<TextBlock Grid.Row="10" Grid.Column="0" Text="Texture Platform *" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Override the game's platform to ensure texture compatibility" />
|
||||
|
|
@ -206,24 +210,27 @@
|
|||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<TextBlock Grid.Row="14" Grid.Column="0" Text="Keep Directory Structure" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Auto-save packages following their game directory" />
|
||||
<TextBlock Grid.Row="14" Grid.Column="0" Text="Serialize Script Bytecode" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<CheckBox Grid.Row="14" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
IsChecked="{Binding ReadScriptData, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 10"/>
|
||||
|
||||
<TextBlock Grid.Row="15" Grid.Column="0" Text="Keep Directory Structure" VerticalAlignment="Center" Margin="0 0 0 5" ToolTip="Auto-save packages following their game directory" />
|
||||
<CheckBox Grid.Row="15" Grid.Column="2" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
IsChecked="{Binding KeepDirectoryStructure, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}" Margin="0 5 0 10"/>
|
||||
|
||||
<TextBlock Grid.Row="15" Grid.Column="0" Text="Local Mapping File" VerticalAlignment="Center" Margin="0 0 0 5"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" />
|
||||
<CheckBox Grid.Row="15" Grid.Column="2" Margin="0 5 0 10"
|
||||
<TextBlock Grid.Row="16" Grid.Column="0" Text="Local Mapping File" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<CheckBox Grid.Row="16" Grid.Column="2" Margin="0 5 0 10"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
|
||||
Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
IsChecked="{Binding SettingsView.MappingEndpoint.Overwrite, Mode=TwoWay}" />
|
||||
|
||||
<TextBlock Grid.Row="16" Grid.Column="0" Text="Mapping File Path" VerticalAlignment="Center" Margin="0 0 0 5"
|
||||
<TextBlock Grid.Row="17" Grid.Column="0" Text="Mapping File Path" VerticalAlignment="Center" Margin="0 0 0 5"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
|
||||
Visibility="{Binding SettingsView.MappingEndpoint.Overwrite, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
<TextBox Grid.Row="16" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 0 0 5" Text="{Binding SettingsView.MappingEndpoint.FilePath, Mode=TwoWay}"
|
||||
<TextBox Grid.Row="17" Grid.Column="2" Grid.ColumnSpan="3" Margin="0 0 0 5" Text="{Binding SettingsView.MappingEndpoint.FilePath, Mode=TwoWay}"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
|
||||
Visibility="{Binding SettingsView.MappingEndpoint.Overwrite, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
<Button Grid.Row="16" Grid.Column="6" Content="..." HorizontalAlignment="Right" Click="OnBrowseMappings" Margin="0 0 0 5"
|
||||
<Button Grid.Row="17" Grid.Column="6" Content="..." HorizontalAlignment="Right" Click="OnBrowseMappings" Margin="0 0 0 5"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}"
|
||||
Visibility="{Binding SettingsView.MappingEndpoint.Overwrite, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
</Grid>
|
||||
|
|
@ -310,6 +317,7 @@
|
|||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
|
|
@ -356,40 +364,45 @@
|
|||
|
||||
<Separator Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="5" Style="{StaticResource CustomSeparator}" />
|
||||
|
||||
<TextBlock Grid.Row="5" Grid.Column="0" Text="Preview Static Meshes" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<CheckBox Grid.Row="5" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
<TextBlock Grid.Row="5" Grid.Column="0" Text="Preview Max Texture Size" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<Slider Grid.Row="5" Grid.Column="2" Grid.ColumnSpan="3" TickPlacement="None" Minimum="4" Maximum="4096" Ticks="4,8,16,32,64,128,256,512,1024,2048,4096"
|
||||
AutoToolTipPlacement="BottomRight" IsMoveToPointEnabled="True" IsSnapToTickEnabled="True" Margin="0 5 0 5"
|
||||
Value="{Binding PreviewMaxTextureSize, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"/>
|
||||
|
||||
<TextBlock Grid.Row="6" Grid.Column="0" Text="Preview Static Meshes" 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 PreviewStaticMeshes, Source={x:Static local:Settings.UserSettings.Default}, Mode=TwoWay}"
|
||||
Style="{DynamicResource {x:Static adonisUi:Styles.ToggleSwitch}}" Margin="0 5 0 5"/>
|
||||
|
||||
<TextBlock Grid.Row="6" Grid.Column="0" Text="Preview Skeletal Meshes" 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}}"
|
||||
<TextBlock Grid.Row="7" Grid.Column="0" Text="Preview Skeletal Meshes" 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 PreviewSkeletalMeshes, 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="Preview Materials" 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="Preview Materials" 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 PreviewMaterials, 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="Preview Levels (.umap)" 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}}"
|
||||
<TextBlock Grid.Row="9" Grid.Column="0" Text="Preview Levels (.umap)" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<CheckBox Grid.Row="9" 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="9" Grid.Column="0" Text="Save Morph Targets in Meshes" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<CheckBox Grid.Row="9" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
<TextBlock Grid.Row="10" Grid.Column="0" Text="Save Morph Targets in Meshes" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<CheckBox Grid.Row="10" 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="10" Grid.Column="0" Text="Handle Skeletons as Empty Meshes" VerticalAlignment="Center" />
|
||||
<CheckBox Grid.Row="10" Grid.Column="2" Grid.ColumnSpan="3" Content="{Binding IsChecked, RelativeSource={RelativeSource Self}, Converter={x:Static converters:BoolToToggleConverter.Instance}}"
|
||||
<TextBlock Grid.Row="11" Grid.Column="0" Text="Handle Skeletons as Empty Meshes" VerticalAlignment="Center" />
|
||||
<CheckBox Grid.Row="11" 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="11" Grid.Column="0" Grid.ColumnSpan="5" Style="{StaticResource CustomSeparator}" />
|
||||
<Separator Grid.Row="12" Grid.Column="0" Grid.ColumnSpan="5" Style="{StaticResource CustomSeparator}" />
|
||||
|
||||
<TextBlock Grid.Row="12" Grid.Column="0" Text="Material Format" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<ComboBox Grid.Row="12" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.MaterialExportFormats}" SelectedItem="{Binding SettingsView.SelectedMaterialExportFormat, Mode=TwoWay}"
|
||||
<TextBlock Grid.Row="13" Grid.Column="0" Text="Material Format" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<ComboBox Grid.Row="13" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.MaterialExportFormats}" SelectedItem="{Binding SettingsView.SelectedMaterialExportFormat, Mode=TwoWay}"
|
||||
DataContext="{Binding DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:Views.SettingsView}}}" Margin="0 0 0 5">
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
|
|
@ -398,8 +411,8 @@
|
|||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
|
||||
<TextBlock Grid.Row="13" Grid.Column="0" Text="Texture Format" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<ComboBox Grid.Row="13" Grid.Column="2" Grid.ColumnSpan="3" ItemsSource="{Binding SettingsView.TextureExportFormats}" SelectedItem="{Binding SettingsView.SelectedTextureExportFormat, Mode=TwoWay}"
|
||||
<TextBlock Grid.Row="14" Grid.Column="0" Text="Texture Format" VerticalAlignment="Center" Margin="0 0 0 5" />
|
||||
<ComboBox Grid.Row="14" 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>
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ public partial class SettingsView
|
|||
{
|
||||
case SettingsOut.ReloadLocres:
|
||||
_applicationView.CUE4Parse.LocalizedResourcesCount = 0;
|
||||
_applicationView.CUE4Parse.LocalResourcesDone = false;
|
||||
_applicationView.CUE4Parse.HotfixedResourcesDone = false;
|
||||
await _applicationView.CUE4Parse.LoadLocalizedResources();
|
||||
break;
|
||||
case SettingsOut.ReloadMappings:
|
||||
|
|
@ -59,6 +61,8 @@ public partial class SettingsView
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_applicationView.CUE4Parse.Provider.ReadScriptData = UserSettings.Default.ReadScriptData;
|
||||
}
|
||||
|
||||
private void OnBrowseOutput(object sender, RoutedEventArgs e)
|
||||
|
|
@ -183,6 +187,19 @@ public partial class SettingsView
|
|||
_applicationView.SettingsView.SelectedOptions = editor.Options;
|
||||
}
|
||||
|
||||
private void OpenMapStructTypes(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var editor = new DictionaryEditor(
|
||||
_applicationView.SettingsView.SelectedMapStructTypes,
|
||||
"MapStructTypes",
|
||||
_applicationView.SettingsView.EnableElements);
|
||||
var result = editor.ShowDialog();
|
||||
if (!result.HasValue || !result.Value)
|
||||
return;
|
||||
|
||||
_applicationView.SettingsView.SelectedMapStructTypes = editor.MapStructTypes;
|
||||
}
|
||||
|
||||
private void OpenAesEndpoint(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var editor = new EndpointEditor(
|
||||
|
|
|
|||
156
FModel/Views/Snooper/Animations/Animation.cs
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using CUE4Parse_Conversion.Animations.PSA;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using CUE4Parse.Utils;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace FModel.Views.Snooper.Animations;
|
||||
|
||||
public class Animation : IDisposable
|
||||
{
|
||||
private readonly UObject _export;
|
||||
private readonly CAnimSet _animSet;
|
||||
|
||||
public readonly string Path;
|
||||
public readonly string Name;
|
||||
public readonly Sequence[] Sequences;
|
||||
public readonly float StartTime; // Animation Start Time
|
||||
public readonly float EndTime; // Animation End Time
|
||||
public readonly float TotalElapsedTime; // Animation Max Time
|
||||
public readonly string TargetSkeleton;
|
||||
|
||||
public int CurrentSequence;
|
||||
public int FrameInSequence; // Current Sequence's Frame to Display
|
||||
|
||||
public string Label =>
|
||||
$"Retarget: {TargetSkeleton}\nSequences: {CurrentSequence + 1}/{Sequences.Length}\nFrames: {FrameInSequence}/{Sequences[CurrentSequence].EndFrame}";
|
||||
public bool IsActive;
|
||||
public bool IsSelected;
|
||||
|
||||
public readonly List<FGuid> AttachedModels;
|
||||
|
||||
public Animation(UObject export)
|
||||
{
|
||||
_export = export;
|
||||
Path = _export.GetPathName();
|
||||
Name = _export.Name;
|
||||
Sequences = Array.Empty<Sequence>();
|
||||
AttachedModels = new List<FGuid>();
|
||||
}
|
||||
|
||||
public Animation(UObject export, CAnimSet animSet) : this(export)
|
||||
{
|
||||
_animSet = animSet;
|
||||
TargetSkeleton = _animSet.Skeleton.Name;
|
||||
|
||||
Sequences = new Sequence[_animSet.Sequences.Count];
|
||||
for (int i = 0; i < Sequences.Length; i++)
|
||||
{
|
||||
Sequences[i] = new Sequence(_animSet.Sequences[i]);
|
||||
|
||||
EndTime = Sequences[i].EndTime;
|
||||
TotalElapsedTime += _animSet.Sequences[i].NumFrames * Sequences[i].TimePerFrame;
|
||||
}
|
||||
|
||||
if (Sequences.Length > 0)
|
||||
StartTime = Sequences[0].StartTime;
|
||||
}
|
||||
|
||||
public Animation(UObject export, CAnimSet animSet, params FGuid[] animatedModels) : this(export, animSet)
|
||||
{
|
||||
AttachedModels.AddRange(animatedModels);
|
||||
}
|
||||
|
||||
public void TimeCalculation(float elapsedTime)
|
||||
{
|
||||
for (int i = 0; i < Sequences.Length; i++)
|
||||
{
|
||||
if (elapsedTime < Sequences[i].EndTime && elapsedTime >= Sequences[i].StartTime)
|
||||
{
|
||||
CurrentSequence = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (elapsedTime >= TotalElapsedTime) Reset();
|
||||
|
||||
var lastEndTime = 0.0f;
|
||||
for (int s = 0; s < CurrentSequence; s++)
|
||||
lastEndTime = Sequences[s].EndTime;
|
||||
|
||||
FrameInSequence = Math.Min(((elapsedTime - lastEndTime) / Sequences[CurrentSequence].TimePerFrame).FloorToInt(), Sequences[CurrentSequence].EndFrame);
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
FrameInSequence = 0;
|
||||
CurrentSequence = 0;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
AttachedModels.Clear();
|
||||
}
|
||||
|
||||
public void ImGuiAnimation(Snooper s, Save saver, ImDrawListPtr drawList, ImFontPtr fontPtr, Vector2 timelineP0, Vector2 treeP0, Vector2 timeStep, Vector2 timeRatio, float y, float t, int i)
|
||||
{
|
||||
var name = $"{Name}##{i}";
|
||||
var p1 = new Vector2(timelineP0.X + StartTime * timeRatio.X + t, y + t);
|
||||
var p2 = new Vector2(timelineP0.X + EndTime * timeRatio.X - t, y + timeStep.Y - t);
|
||||
|
||||
ImGui.SetCursorScreenPos(p1);
|
||||
ImGui.InvisibleButton($"timeline_sequencetracker_{name}", new Vector2(EndTime * timeRatio.X - t, timeStep.Y - t), ImGuiButtonFlags.MouseButtonLeft);
|
||||
IsActive = ImGui.IsItemActive();
|
||||
IsSelected = s.Renderer.Options.SelectedAnimation == i;
|
||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
|
||||
{
|
||||
s.Renderer.Options.SelectAnimation(i);
|
||||
}
|
||||
Popup(s, saver, i);
|
||||
|
||||
drawList.AddRectFilled(p1, p2, IsSelected ? 0xFF48B048 : 0xFF175F17, 5.0f, ImDrawFlags.RoundCornersTop);
|
||||
for (int j = 0; j < Sequences.Length; j++)
|
||||
{
|
||||
Sequences[j].DrawSequence(drawList, fontPtr, timelineP0.X, p2, timeStep, timeRatio, t, IsSelected);
|
||||
}
|
||||
|
||||
ImGui.SetCursorScreenPos(treeP0 with { Y = p1.Y });
|
||||
if (ImGui.Selectable(name, s.Renderer.Options.SelectedAnimation == i, ImGuiSelectableFlags.SpanAllColumns, new Vector2(p1.X - treeP0.X, timeStep.Y - t - t)))
|
||||
{
|
||||
s.Renderer.Options.SelectAnimation(i);
|
||||
}
|
||||
Popup(s, saver, i);
|
||||
}
|
||||
|
||||
private void Popup(Snooper s, Save saver, int i)
|
||||
{
|
||||
SnimGui.Popup(() =>
|
||||
{
|
||||
s.Renderer.Options.SelectAnimation(i);
|
||||
if (ImGui.BeginMenu("Animate"))
|
||||
{
|
||||
foreach ((var guid, var model) in s.Renderer.Options.Models)
|
||||
{
|
||||
var selected = AttachedModels.Contains(guid);
|
||||
if (ImGui.MenuItem(model.Name, null, selected, (model.HasSkeleton && !model.Skeleton.IsAnimated) || selected))
|
||||
{
|
||||
if (selected) AttachedModels.Remove(guid); else AttachedModels.Add(guid);
|
||||
model.Skeleton.ResetAnimatedData(true);
|
||||
if (!selected) model.Skeleton.Animate(_animSet, s.Renderer.AnimateWithRotationOnly);
|
||||
}
|
||||
}
|
||||
ImGui.EndMenu();
|
||||
}
|
||||
if (ImGui.MenuItem("Save"))
|
||||
{
|
||||
s.WindowShouldFreeze(true);
|
||||
saver.Value = s.Renderer.Options.TrySave(_export, out saver.Label, out saver.Path);
|
||||
s.WindowShouldFreeze(false);
|
||||
}
|
||||
ImGui.Separator();
|
||||
if (ImGui.MenuItem("Copy Path to Clipboard")) ImGui.SetClipboardText(Path);
|
||||
});
|
||||
}
|
||||
}
|
||||
25
FModel/Views/Snooper/Animations/Bone.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
namespace FModel.Views.Snooper.Animations;
|
||||
|
||||
public class Bone
|
||||
{
|
||||
public readonly int Index;
|
||||
public readonly int ParentIndex;
|
||||
public readonly Transform Rest;
|
||||
public string LoweredParentName;
|
||||
|
||||
public int SkeletonIndex = -1;
|
||||
public bool IsAnimated;
|
||||
|
||||
public Bone(int i, int p, Transform t)
|
||||
{
|
||||
Index = i;
|
||||
ParentIndex = p;
|
||||
Rest = t;
|
||||
}
|
||||
|
||||
public bool IsRoot => Index == 0 && ParentIndex == -1 && string.IsNullOrEmpty(LoweredParentName);
|
||||
public bool IsMapped => SkeletonIndex > -1;
|
||||
public bool IsNative => Index == SkeletonIndex;
|
||||
|
||||
public override string ToString() => $"Mesh Ref '{Index}' is Skel Ref '{SkeletonIndex}' ({IsAnimated})";
|
||||
}
|
||||
49
FModel/Views/Snooper/Animations/Sequence.cs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
using System.Numerics;
|
||||
using CUE4Parse_Conversion.Animations.PSA;
|
||||
using CUE4Parse.Utils;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace FModel.Views.Snooper.Animations;
|
||||
|
||||
public class Sequence
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly float TimePerFrame;
|
||||
public readonly float StartTime;
|
||||
public readonly float Duration;
|
||||
public readonly float EndTime;
|
||||
public readonly int EndFrame;
|
||||
public readonly int LoopingCount;
|
||||
public readonly bool IsAdditive;
|
||||
|
||||
public Sequence(CAnimSequence sequence)
|
||||
{
|
||||
Name = sequence.Name;
|
||||
TimePerFrame = 1.0f / sequence.FramesPerSecond;
|
||||
StartTime = sequence.StartPos;
|
||||
Duration = sequence.AnimEndTime;
|
||||
EndTime = StartTime + Duration;
|
||||
EndFrame = (Duration / TimePerFrame).FloorToInt() - 1;
|
||||
LoopingCount = sequence.LoopingCount;
|
||||
IsAdditive = sequence.IsAdditive;
|
||||
}
|
||||
|
||||
public void DrawSequence(ImDrawListPtr drawList, ImFontPtr fontPtr, float x, Vector2 p2, Vector2 timeStep, Vector2 timeRatio, float t, bool animSelected)
|
||||
{
|
||||
var halfThickness = t / 2.0f;
|
||||
var q1 = new Vector2(x + StartTime * timeRatio.X + t + halfThickness, p2.Y - timeStep.Y / 2.0f);
|
||||
var q2 = p2 with { X = x + EndTime * timeRatio.X - t * 2.0f };
|
||||
|
||||
drawList.PushClipRect(q1, q2 with { X = q2.X + t }, true);
|
||||
|
||||
var lineColor = animSelected ? 0xA0FFFFFF : 0x50FFFFFF;
|
||||
drawList.AddLine(new Vector2(q1.X, q2.Y), q1, lineColor, 1.0f);
|
||||
drawList.AddLine(q1, new Vector2(q2.X, q1.Y), lineColor, 1.0f);
|
||||
drawList.AddLine(new Vector2(q2.X, q1.Y), q2, lineColor, 1.0f);
|
||||
|
||||
if (IsAdditive)
|
||||
drawList.AddText(fontPtr, 12 * ImGui.GetWindowDpiScale(), new Vector2(q1.X + t, q1.Y + halfThickness), animSelected ? 0xFFFFFFFF : 0x50FFFFFF, "Is Additive");
|
||||
|
||||
drawList.PopClipRect();
|
||||
}
|
||||
}
|
||||
246
FModel/Views/Snooper/Animations/Skeleton.cs
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using CUE4Parse_Conversion.Animations.PSA;
|
||||
using CUE4Parse.UE4.Assets.Exports.Animation;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using FModel.Views.Snooper.Buffers;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using Serilog;
|
||||
|
||||
namespace FModel.Views.Snooper.Animations;
|
||||
|
||||
public class Skeleton : IDisposable
|
||||
{
|
||||
private int _handle;
|
||||
private BufferObject<Matrix4x4> _ssbo;
|
||||
|
||||
public string Name;
|
||||
public readonly Dictionary<string, Bone> BonesByLoweredName;
|
||||
|
||||
private int _previousAnimationSequence;
|
||||
private int _previousSequenceFrame;
|
||||
private Transform[][][] _animatedBonesTransform; // [sequence][bone][frame]
|
||||
private readonly Matrix4x4[] _invertedBonesMatrix;
|
||||
public int BoneCount => _invertedBonesMatrix.Length;
|
||||
public bool IsAnimated => _animatedBonesTransform.Length > 0;
|
||||
|
||||
public Skeleton()
|
||||
{
|
||||
BonesByLoweredName = new Dictionary<string, Bone>();
|
||||
_animatedBonesTransform = Array.Empty<Transform[][]>();
|
||||
_invertedBonesMatrix = Array.Empty<Matrix4x4>();
|
||||
}
|
||||
|
||||
public Skeleton(FReferenceSkeleton referenceSkeleton) : this()
|
||||
{
|
||||
_invertedBonesMatrix = new Matrix4x4[referenceSkeleton.FinalRefBoneInfo.Length];
|
||||
for (int boneIndex = 0; boneIndex < _invertedBonesMatrix.Length; boneIndex++)
|
||||
{
|
||||
var info = referenceSkeleton.FinalRefBoneInfo[boneIndex];
|
||||
var boneTransform = new Transform
|
||||
{
|
||||
Rotation = referenceSkeleton.FinalRefBonePose[boneIndex].Rotation,
|
||||
Position = referenceSkeleton.FinalRefBonePose[boneIndex].Translation * Constants.SCALE_DOWN_RATIO,
|
||||
Scale = referenceSkeleton.FinalRefBonePose[boneIndex].Scale3D
|
||||
};
|
||||
|
||||
var bone = new Bone(boneIndex, info.ParentIndex, boneTransform);
|
||||
if (!bone.IsRoot)
|
||||
{
|
||||
bone.LoweredParentName =
|
||||
referenceSkeleton.FinalRefBoneInfo[bone.ParentIndex].Name.Text.ToLower();
|
||||
bone.Rest.Relation = BonesByLoweredName[bone.LoweredParentName].Rest.Matrix;
|
||||
}
|
||||
|
||||
BonesByLoweredName[info.Name.Text.ToLower()] = bone;
|
||||
|
||||
Matrix4x4.Invert(boneTransform.Matrix, out var inverted);
|
||||
_invertedBonesMatrix[bone.Index] = inverted;
|
||||
}
|
||||
}
|
||||
|
||||
public void Animate(CAnimSet anim, bool rotationOnly)
|
||||
{
|
||||
MapSkeleton(anim);
|
||||
|
||||
_animatedBonesTransform = new Transform[anim.Sequences.Count][][];
|
||||
for (int s = 0; s < _animatedBonesTransform.Length; s++)
|
||||
{
|
||||
var sequence = anim.Sequences[s];
|
||||
_animatedBonesTransform[s] = new Transform[BoneCount][];
|
||||
foreach (var bone in BonesByLoweredName.Values)
|
||||
{
|
||||
_animatedBonesTransform[s][bone.Index] = new Transform[sequence.NumFrames];
|
||||
|
||||
var skeletonBoneIndex = bone.SkeletonIndex;
|
||||
if (sequence.OriginalSequence.FindTrackForBoneIndex(skeletonBoneIndex) < 0)
|
||||
{
|
||||
bone.IsAnimated |= false;
|
||||
for (int frame = 0; frame < _animatedBonesTransform[s][bone.Index].Length; frame++)
|
||||
{
|
||||
_animatedBonesTransform[s][bone.Index][frame] = new Transform
|
||||
{
|
||||
Relation = bone.IsRoot ? bone.Rest.Relation :
|
||||
bone.Rest.LocalMatrix * _animatedBonesTransform[s][bone.ParentIndex][frame].Matrix
|
||||
};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bone.IsAnimated |= true;
|
||||
for (int frame = 0; frame < _animatedBonesTransform[s][bone.Index].Length; frame++)
|
||||
{
|
||||
var boneOrientation = bone.Rest.Rotation;
|
||||
var bonePosition = bone.Rest.Position;
|
||||
var boneScale = bone.Rest.Scale;
|
||||
|
||||
sequence.Tracks[skeletonBoneIndex].GetBoneTransform(frame, sequence.NumFrames, ref boneOrientation, ref bonePosition, ref boneScale);
|
||||
|
||||
switch (anim.Skeleton.BoneTree[skeletonBoneIndex])
|
||||
{
|
||||
case EBoneTranslationRetargetingMode.Skeleton when !rotationOnly:
|
||||
{
|
||||
var targetTransform = sequence.RetargetBasePose?[skeletonBoneIndex] ?? anim.Skeleton.ReferenceSkeleton.FinalRefBonePose[skeletonBoneIndex];
|
||||
bonePosition = targetTransform.Translation;
|
||||
break;
|
||||
}
|
||||
case EBoneTranslationRetargetingMode.AnimationScaled when !rotationOnly:
|
||||
{
|
||||
var sourceTranslationLength = (bone.Rest.Position / Constants.SCALE_DOWN_RATIO).Size();
|
||||
if (sourceTranslationLength > UnrealMath.KindaSmallNumber)
|
||||
{
|
||||
var targetTranslationLength = sequence.RetargetBasePose?[skeletonBoneIndex].Translation.Size() ?? anim.Skeleton.ReferenceSkeleton.FinalRefBonePose[skeletonBoneIndex].Translation.Size();
|
||||
bonePosition.Scale(targetTranslationLength / sourceTranslationLength);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EBoneTranslationRetargetingMode.AnimationRelative when !rotationOnly:
|
||||
{
|
||||
// can't tell if it's working or not
|
||||
var sourceSkelTrans = bone.Rest.Position / Constants.SCALE_DOWN_RATIO;
|
||||
var refPoseTransform = sequence.RetargetBasePose?[skeletonBoneIndex] ?? anim.Skeleton.ReferenceSkeleton.FinalRefBonePose[skeletonBoneIndex];
|
||||
|
||||
boneOrientation = boneOrientation * FQuat.Conjugate(bone.Rest.Rotation) * refPoseTransform.Rotation;
|
||||
bonePosition += refPoseTransform.Translation - sourceSkelTrans;
|
||||
boneScale *= refPoseTransform.Scale3D * bone.Rest.Scale;
|
||||
boneOrientation.Normalize();
|
||||
break;
|
||||
}
|
||||
case EBoneTranslationRetargetingMode.OrientAndScale when !rotationOnly:
|
||||
{
|
||||
var sourceSkelTrans = bone.Rest.Position / Constants.SCALE_DOWN_RATIO;
|
||||
var targetSkelTrans = sequence.RetargetBasePose?[skeletonBoneIndex].Translation ?? anim.Skeleton.ReferenceSkeleton.FinalRefBonePose[skeletonBoneIndex].Translation;
|
||||
|
||||
if (!sourceSkelTrans.Equals(targetSkelTrans))
|
||||
{
|
||||
var sourceSkelTransLength = sourceSkelTrans.Size();
|
||||
var targetSkelTransLength = targetSkelTrans.Size();
|
||||
if (!UnrealMath.IsNearlyZero(sourceSkelTransLength * targetSkelTransLength))
|
||||
{
|
||||
var sourceSkelTransDir = sourceSkelTrans / sourceSkelTransLength;
|
||||
var targetSkelTransDir = targetSkelTrans / targetSkelTransLength;
|
||||
|
||||
var deltaRotation = FQuat.FindBetweenNormals(sourceSkelTransDir, targetSkelTransDir);
|
||||
var scale = targetSkelTransLength / sourceSkelTransLength;
|
||||
bonePosition = deltaRotation.RotateVector(bonePosition) * scale;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_animatedBonesTransform[s][bone.Index][frame] = new Transform
|
||||
{
|
||||
Relation = bone.IsRoot ? bone.Rest.Relation : _animatedBonesTransform[s][bone.ParentIndex][frame].Matrix,
|
||||
Rotation = boneOrientation,
|
||||
Position = rotationOnly ? bone.Rest.Position : bonePosition * Constants.SCALE_DOWN_RATIO,
|
||||
Scale = boneScale
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void MapSkeleton(CAnimSet anim)
|
||||
{
|
||||
ResetAnimatedData();
|
||||
|
||||
// map bones
|
||||
for (int boneIndex = 0; boneIndex < anim.Skeleton.BoneCount; boneIndex++)
|
||||
{
|
||||
var info = anim.Skeleton.ReferenceSkeleton.FinalRefBoneInfo[boneIndex];
|
||||
if (!BonesByLoweredName.TryGetValue(info.Name.Text.ToLower(), out var bone))
|
||||
continue;
|
||||
|
||||
bone.SkeletonIndex = boneIndex;
|
||||
bone.IsAnimated = false;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
foreach ((var boneName, var bone) in BonesByLoweredName)
|
||||
{
|
||||
if (bone.IsRoot || bone.IsMapped) // assuming root bone always is mapped
|
||||
continue;
|
||||
|
||||
Log.Warning($"{Name} Bone Mismatch: {boneName} ({bone.Index}) was not present in the anim's target skeleton");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public void ResetAnimatedData(bool full = false)
|
||||
{
|
||||
foreach (var bone in BonesByLoweredName.Values)
|
||||
{
|
||||
bone.SkeletonIndex = -1;
|
||||
bone.IsAnimated = false;
|
||||
}
|
||||
|
||||
if (!full) return;
|
||||
_animatedBonesTransform = Array.Empty<Transform[][]>();
|
||||
_ssbo.UpdateRange(BoneCount, Matrix4x4.Identity);
|
||||
}
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
_handle = GL.CreateProgram();
|
||||
|
||||
_ssbo = new BufferObject<Matrix4x4>(BoneCount, BufferTarget.ShaderStorageBuffer);
|
||||
_ssbo.UpdateRange(BoneCount, Matrix4x4.Identity);
|
||||
}
|
||||
|
||||
public void UpdateAnimationMatrices(int currentSequence, int frameInSequence)
|
||||
{
|
||||
if (!IsAnimated) return;
|
||||
|
||||
_previousAnimationSequence = currentSequence;
|
||||
if (_previousSequenceFrame == frameInSequence) return;
|
||||
_previousSequenceFrame = frameInSequence;
|
||||
|
||||
_ssbo.Bind();
|
||||
for (int boneIndex = 0; boneIndex < BoneCount; boneIndex++) // interpolate here
|
||||
_ssbo.Update(boneIndex, _invertedBonesMatrix[boneIndex] * _animatedBonesTransform[_previousAnimationSequence][boneIndex][_previousSequenceFrame].Matrix);
|
||||
_ssbo.Unbind();
|
||||
}
|
||||
|
||||
public Matrix4x4 GetBoneMatrix(Bone bone)
|
||||
{
|
||||
return IsAnimated
|
||||
? _animatedBonesTransform[_previousAnimationSequence][bone.Index][_previousSequenceFrame].Matrix
|
||||
: bone.Rest.Matrix;
|
||||
}
|
||||
|
||||
public void Render()
|
||||
{
|
||||
_ssbo.BindBufferBase(1);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
BonesByLoweredName.Clear();
|
||||
|
||||
_ssbo?.Dispose();
|
||||
GL.DeleteProgram(_handle);
|
||||
}
|
||||
}
|
||||
221
FModel/Views/Snooper/Animations/TimeTracker.cs
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using FModel.Views.Snooper.Shading;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace FModel.Views.Snooper.Animations;
|
||||
|
||||
public enum ETrackerType
|
||||
{
|
||||
Start,
|
||||
Frame,
|
||||
InBetween,
|
||||
End
|
||||
}
|
||||
|
||||
public class TimeTracker : IDisposable
|
||||
{
|
||||
public bool IsPaused;
|
||||
public bool IsActive;
|
||||
public float ElapsedTime;
|
||||
public float MaxElapsedTime;
|
||||
|
||||
public TimeTracker()
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
public void Update(float deltaSeconds)
|
||||
{
|
||||
if (IsPaused || IsActive) return;
|
||||
ElapsedTime += deltaSeconds;
|
||||
if (ElapsedTime >= MaxElapsedTime) Reset(false);
|
||||
}
|
||||
|
||||
public void SafeSetElapsedTime(float elapsedTime)
|
||||
{
|
||||
ElapsedTime = Math.Clamp(elapsedTime, 0.0f, MaxElapsedTime);
|
||||
}
|
||||
|
||||
public void SafeSetMaxElapsedTime(float maxElapsedTime)
|
||||
{
|
||||
MaxElapsedTime = MathF.Max(maxElapsedTime, MaxElapsedTime);
|
||||
}
|
||||
|
||||
public void Reset(bool doMet = true)
|
||||
{
|
||||
IsPaused = false;
|
||||
ElapsedTime = 0.0f;
|
||||
if (doMet) MaxElapsedTime = 0.01f;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
private readonly string[] _icons = { "tl_forward", "tl_pause", "tl_rewind" };
|
||||
public void ImGuiTimeline(Snooper s, Save saver, Dictionary<string, Texture> icons, List<Animation> animations, Vector2 outliner, ImFontPtr fontPtr)
|
||||
{
|
||||
var dpiScale = ImGui.GetWindowDpiScale();
|
||||
var thickness = 2.0f * dpiScale;
|
||||
var buttonWidth = 14.0f * dpiScale;
|
||||
var timeHeight = 10.0f * dpiScale;
|
||||
var timeBarHeight = timeHeight * 2.0f;
|
||||
var timeStep = new Vector2(50 * dpiScale, 25 * dpiScale);
|
||||
|
||||
var treeP0 = ImGui.GetCursorScreenPos();
|
||||
var canvasSize = ImGui.GetContentRegionAvail();
|
||||
var canvasMaxY = MathF.Max(canvasSize.Y, timeBarHeight + timeStep.Y * animations.Count);
|
||||
ImGui.BeginChild("timeline_child", canvasSize with { Y = canvasMaxY });
|
||||
|
||||
var timelineP1 = new Vector2(treeP0.X + canvasSize.X, treeP0.Y + canvasMaxY);
|
||||
var treeP1 = timelineP1 with { X = treeP0.X + outliner.X };
|
||||
|
||||
var timelineP0 = treeP0 with { X = treeP1.X + thickness };
|
||||
var timelineSize = timelineP1 - timelineP0;
|
||||
var timeRatio = timelineSize / MaxElapsedTime;
|
||||
|
||||
var drawList = ImGui.GetWindowDrawList();
|
||||
drawList.PushClipRect(treeP0, timelineP1, true);
|
||||
drawList.AddRectFilled(treeP0, treeP1, 0xFF1F1C1C);
|
||||
drawList.AddRectFilled(timelineP0, timelineP1 with { Y = timelineP0.Y + timeBarHeight }, 0xFF141414);
|
||||
drawList.AddRectFilled(timelineP0 with { Y = timelineP0.Y + timeBarHeight }, timelineP1, 0xFF242424);
|
||||
drawList.AddLine(new Vector2(treeP1.X, treeP0.Y), treeP1, 0xFF504545, thickness);
|
||||
drawList.AddLine(treeP0 with { Y = timelineP0.Y + timeBarHeight }, timelineP1 with { Y = timelineP0.Y + timeBarHeight }, 0x50504545, thickness);
|
||||
|
||||
// adding margin
|
||||
var margin = 5.0f * dpiScale;
|
||||
treeP0.X += margin;
|
||||
treeP1.X -= margin;
|
||||
|
||||
// control buttons
|
||||
for (int i = 0; i < _icons.Length; i++)
|
||||
{
|
||||
var x = buttonWidth * 2.0f * i;
|
||||
ImGui.SetCursorScreenPos(treeP0 with { X = treeP1.X - x - buttonWidth * 2.0f + thickness });
|
||||
if (ImGui.ImageButton($"timeline_actions_{_icons[i]}", icons[i == 1 ? IsPaused ? "tl_play" : "tl_pause" : _icons[i]].GetPointer(), new Vector2(buttonWidth)))
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case 0:
|
||||
SafeSetElapsedTime(ElapsedTime + timeStep.X / timeRatio.X);
|
||||
break;
|
||||
case 1:
|
||||
IsPaused = !IsPaused;
|
||||
break;
|
||||
case 2:
|
||||
SafeSetElapsedTime(ElapsedTime - timeStep.X / timeRatio.X);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drawList.AddText(treeP0 with { Y = treeP0.Y + thickness }, 0xA0FFFFFF, $"{ElapsedTime:F1}/{MaxElapsedTime:F1} seconds");
|
||||
|
||||
ImGui.SetCursorScreenPos(timelineP0);
|
||||
ImGui.InvisibleButton("timeline_timetracker_canvas", timelineSize with { Y = timeBarHeight }, ImGuiButtonFlags.MouseButtonLeft);
|
||||
IsActive = ImGui.IsItemActive();
|
||||
if (IsActive && ImGui.IsMouseDragging(ImGuiMouseButton.Left))
|
||||
{
|
||||
var mousePosCanvas = ImGui.GetIO().MousePos - timelineP0;
|
||||
SafeSetElapsedTime(mousePosCanvas.X / timelineSize.X * MaxElapsedTime);
|
||||
foreach (var animation in animations)
|
||||
{
|
||||
animation.TimeCalculation(ElapsedTime);
|
||||
}
|
||||
}
|
||||
|
||||
{ // draw time + time grid
|
||||
for (float x = 0; x < timelineSize.X; x += timeStep.X)
|
||||
{
|
||||
var cursor = timelineP0.X + x;
|
||||
drawList.AddLine(new Vector2(cursor, timelineP0.Y + timeHeight + 2.5f), new Vector2(cursor, timelineP0.Y + timeBarHeight), 0xA0FFFFFF);
|
||||
drawList.AddLine(new Vector2(cursor, timelineP0.Y + timeBarHeight), timelineP1 with { X = cursor }, 0x28C8C8C8);
|
||||
drawList.AddText(fontPtr, 14 * dpiScale, new Vector2(cursor + 4, timelineP0.Y + 7.5f), 0x50FFFFFF, $"{x / timeRatio.X:F1}s");
|
||||
}
|
||||
|
||||
for (float y = timeBarHeight; y < timelineSize.Y; y += timeStep.Y)
|
||||
{
|
||||
drawList.AddLine(timelineP0 with { Y = timelineP0.Y + y }, timelineP1 with { Y = timelineP0.Y + y }, 0x28C8C8C8);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.SelectableTextAlign, new Vector2(0.0f, 0.5f));
|
||||
for (int i = 0; i < animations.Count; i++)
|
||||
{
|
||||
var y = timelineP0.Y + timeBarHeight + timeStep.Y * i;
|
||||
animations[i].ImGuiAnimation(s, saver, drawList, fontPtr, timelineP0, treeP0, timeStep, timeRatio, y, thickness, i);
|
||||
DrawSeparator(drawList, timelineP0, y + timeStep.Y, animations[i].EndTime * timeRatio.X, timeHeight, timeBarHeight, ETrackerType.End);
|
||||
}
|
||||
ImGui.PopStyleVar();
|
||||
|
||||
for (int i = 0; i < animations.Count; i++)
|
||||
{
|
||||
var y = timelineP0.Y + timeBarHeight + timeStep.Y * i;
|
||||
for (int j = 0; j < animations[i].Sequences.Length - 1; j++)
|
||||
{
|
||||
DrawSeparator(drawList, timelineP0, y + timeStep.Y - thickness, animations[i].Sequences[j].EndTime * timeRatio.X - 0.5f, timeHeight, timeBarHeight, ETrackerType.InBetween);
|
||||
}
|
||||
}
|
||||
|
||||
DrawSeparator(drawList, timelineP0, timelineP1.Y, ElapsedTime * timeRatio.X, timeHeight, timeBarHeight, ETrackerType.Frame);
|
||||
|
||||
drawList.PopClipRect();
|
||||
ImGui.EndChild();
|
||||
}
|
||||
|
||||
private void DrawSeparator(ImDrawListPtr drawList, Vector2 origin, float y, float time, float timeHeight, float timeBarHeight, ETrackerType separatorType)
|
||||
{
|
||||
float size = separatorType switch
|
||||
{
|
||||
ETrackerType.Frame => 5,
|
||||
ETrackerType.End => 5,
|
||||
ETrackerType.InBetween => 7.5f,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(separatorType), separatorType, null)
|
||||
};
|
||||
|
||||
Vector2 p1 = separatorType switch
|
||||
{
|
||||
ETrackerType.Frame => new Vector2(origin.X + time, origin.Y + timeBarHeight),
|
||||
ETrackerType.End => origin with { X = origin.X + time },
|
||||
ETrackerType.InBetween => origin with { X = origin.X + time },
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(separatorType), separatorType, null)
|
||||
};
|
||||
var p2 = p1 with { Y = y };
|
||||
|
||||
uint color = separatorType switch
|
||||
{
|
||||
ETrackerType.Frame => 0xFF6F6F6F,
|
||||
ETrackerType.End => 0xFF2E3E82,
|
||||
ETrackerType.InBetween => 0xA0FFFFFF,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(separatorType), separatorType, null)
|
||||
};
|
||||
|
||||
switch (separatorType)
|
||||
{
|
||||
case ETrackerType.Frame:
|
||||
color = 0xFF30478C;
|
||||
var xl = p1.X - size;
|
||||
var xr = p1.X + size;
|
||||
var yb = origin.Y + timeBarHeight - timeHeight / 2.0f;
|
||||
|
||||
drawList.AddLine(p1, p2, color, 1f);
|
||||
drawList.AddQuadFilled(origin with { X = xl }, origin with { X = xr }, new Vector2(xr, yb), new Vector2(xl, yb), color);
|
||||
drawList.AddTriangleFilled(new Vector2(xl, yb), new Vector2(xr, yb), p1, color);
|
||||
break;
|
||||
case ETrackerType.End:
|
||||
drawList.AddLine(p1, p2, color, 1f);
|
||||
drawList.AddTriangleFilled(p1, p1 with { X = p1.X - size }, p1 with { Y = p1.Y + size }, color);
|
||||
break;
|
||||
case ETrackerType.InBetween:
|
||||
p1.Y += timeBarHeight;
|
||||
drawList.AddLine(p1, p2, color, 1f);
|
||||
drawList.AddTriangleFilled(p1, new Vector2(p1.X - size / 2.0f, p1.Y - size), new Vector2(p1.X + size / 2.0f, p1.Y - size), color);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(separatorType), separatorType, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -21,6 +21,18 @@ public class BufferObject<TDataType> : IDisposable where TDataType : unmanaged
|
|||
GL.BufferData(bufferTarget, data.Length * sizeof(TDataType), data, BufferUsageHint.StaticDraw);
|
||||
}
|
||||
|
||||
public unsafe BufferObject(int length, BufferTarget bufferTarget) : this(bufferTarget)
|
||||
{
|
||||
GL.BufferData(bufferTarget, length * sizeof(TDataType), IntPtr.Zero, BufferUsageHint.DynamicDraw);
|
||||
}
|
||||
|
||||
public void UpdateRange(int count, TDataType data)
|
||||
{
|
||||
Bind();
|
||||
for (int i = 0; i < count; i++) Update(i, data);
|
||||
Unbind();
|
||||
}
|
||||
|
||||
public unsafe void Update(int offset, TDataType data)
|
||||
{
|
||||
GL.BufferSubData(_bufferTarget, (IntPtr) (offset * sizeof(TDataType)), sizeof(TDataType), ref data);
|
||||
|
|
@ -36,6 +48,13 @@ public class BufferObject<TDataType> : IDisposable where TDataType : unmanaged
|
|||
GL.BindBuffer(_bufferTarget, _handle);
|
||||
}
|
||||
|
||||
public void BindBufferBase(int index)
|
||||
{
|
||||
if (_bufferTarget != BufferTarget.ShaderStorageBuffer)
|
||||
throw new ArgumentException("BindBufferBase is not allowed for anything but Shader Storage Buffers");
|
||||
GL.BindBufferBase(BufferRangeTarget.ShaderStorageBuffer, index, _handle);
|
||||
}
|
||||
|
||||
public void Unbind()
|
||||
{
|
||||
GL.BindBuffer(_bufferTarget, 0);
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ public class PickingTexture : IDisposable
|
|||
_shader.SetUniform("uD", guid.D);
|
||||
|
||||
if (!model.Show) continue;
|
||||
model.SimpleRender(_shader);
|
||||
model.PickingRender(_shader);
|
||||
}
|
||||
|
||||
Bind(0);
|
||||
|
|
@ -98,6 +98,8 @@ public class PickingTexture : IDisposable
|
|||
return pixel;
|
||||
}
|
||||
|
||||
public IntPtr GetPointer() => (IntPtr) _pickingTexture;
|
||||
|
||||
public void WindowResized(int width, int height)
|
||||
{
|
||||
_width = width;
|
||||
|
|
|
|||
|
|
@ -1,82 +1,170 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using FModel.Settings;
|
||||
using ImGuiNET;
|
||||
using OpenTK.Windowing.GraphicsLibraryFramework;
|
||||
|
||||
namespace FModel.Views.Snooper;
|
||||
|
||||
public class Camera
|
||||
{
|
||||
public enum WorldMode
|
||||
{
|
||||
FlyCam,
|
||||
Arcball
|
||||
}
|
||||
|
||||
public Vector3 Position;
|
||||
public Vector3 Direction;
|
||||
public Vector3 Up = Vector3.UnitY;
|
||||
public WorldMode Mode;
|
||||
public Vector3 PositionArc => Position - Direction;
|
||||
public Vector3 DirectionArc => Direction - Position;
|
||||
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 Near => 0.01f;
|
||||
public float AspectRatio = 16f / 9f;
|
||||
|
||||
public Camera()
|
||||
{
|
||||
Position = new Vector3(0, 1, 1);
|
||||
Direction = Vector3.Zero;
|
||||
|
||||
InitDirection();
|
||||
Mode = UserSettings.Default.CameraMode;
|
||||
}
|
||||
|
||||
public Camera(Vector3 position, Vector3 direction, float near, float far, float speed)
|
||||
public void Setup(FBox box) => Teleport(FVector.ZeroVector, box, true);
|
||||
public void Teleport(Vector3 instancePos, FBox box, bool updateAll = false)
|
||||
{
|
||||
Position = position;
|
||||
Direction = direction;
|
||||
Near = near;
|
||||
Far = far;
|
||||
Speed = speed;
|
||||
box.GetCenterAndExtents(out var center, out var extents);
|
||||
center += new FVector(instancePos.X, instancePos.Z, instancePos.Y);
|
||||
var distance = extents.AbsMax();
|
||||
|
||||
InitDirection();
|
||||
Position = new Vector3(instancePos.X, center.Z, instancePos.Z + distance * 2);
|
||||
Direction = new Vector3(center.X, center.Z, center.Y);
|
||||
if (updateAll)
|
||||
{
|
||||
Far = Math.Max(Far, box.Max.AbsMax() * 50f);
|
||||
Speed = Math.Max(Speed, distance);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitDirection()
|
||||
public void Modify(Vector2 mouseDelta)
|
||||
{
|
||||
// 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));
|
||||
var lookSensitivity = Mode switch
|
||||
{
|
||||
WorldMode.FlyCam => 0.002f,
|
||||
WorldMode.Arcball => 0.003f,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
mouseDelta *= lookSensitivity;
|
||||
|
||||
var rotationX = Matrix4x4.CreateFromAxisAngle(-Up, mouseDelta.X);
|
||||
switch (Mode)
|
||||
{
|
||||
case WorldMode.FlyCam:
|
||||
{
|
||||
Direction = Vector3.Transform(DirectionArc, rotationX) + Position;
|
||||
|
||||
var right = Vector3.Normalize(Vector3.Cross(Up, DirectionArc));
|
||||
var rotationY = Matrix4x4.CreateFromAxisAngle(right, mouseDelta.Y);
|
||||
Direction = Vector3.Transform(DirectionArc, rotationY) + Position;
|
||||
break;
|
||||
}
|
||||
case WorldMode.Arcball:
|
||||
{
|
||||
Position = Vector3.Transform(PositionArc, rotationX) + Direction;
|
||||
|
||||
var right = Vector3.Normalize(Vector3.Cross(-Up, PositionArc));
|
||||
var rotationY = Matrix4x4.CreateFromAxisAngle(right, mouseDelta.Y);
|
||||
Position = Vector3.Transform(PositionArc, rotationY) + Direction;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
public void ModifyZoom(float zoomAmount)
|
||||
public void Modify(KeyboardState keyboard, float time)
|
||||
{
|
||||
if (!keyboard.IsAnyKeyDown) return;
|
||||
var multiplier = keyboard.IsKeyDown(Keys.LeftShift) ? 2f : 1f;
|
||||
var moveSpeed = Speed * multiplier * time;
|
||||
var moveAxis = Vector3.Normalize(-PositionArc);
|
||||
var panAxis = Vector3.Normalize(Vector3.Cross(moveAxis, Up));
|
||||
|
||||
switch (Mode)
|
||||
{
|
||||
case WorldMode.FlyCam:
|
||||
{
|
||||
if (keyboard.IsKeyDown(Keys.W)) // forward
|
||||
{
|
||||
var d = moveSpeed * moveAxis;
|
||||
Position += d;
|
||||
Direction += d;
|
||||
}
|
||||
if (keyboard.IsKeyDown(Keys.S)) // backward
|
||||
{
|
||||
var d = moveSpeed * moveAxis;
|
||||
Position -= d;
|
||||
Direction -= d;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WorldMode.Arcball:
|
||||
{
|
||||
if (keyboard.IsKeyDown(Keys.W)) // forward
|
||||
Position += moveSpeed * moveAxis;
|
||||
if (keyboard.IsKeyDown(Keys.S)) // backward
|
||||
Position -= moveSpeed * moveAxis;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
if (keyboard.IsKeyDown(Keys.A)) // left
|
||||
{
|
||||
var d = panAxis * moveSpeed;
|
||||
Position -= d;
|
||||
Direction -= d;
|
||||
}
|
||||
if (keyboard.IsKeyDown(Keys.D)) // right
|
||||
{
|
||||
var d = panAxis * moveSpeed;
|
||||
Position += d;
|
||||
Direction += d;
|
||||
}
|
||||
if (keyboard.IsKeyDown(Keys.E)) // up
|
||||
{
|
||||
var d = moveSpeed * Up;
|
||||
Position += d;
|
||||
Direction += d;
|
||||
}
|
||||
if (keyboard.IsKeyDown(Keys.Q)) // down
|
||||
{
|
||||
var d = moveSpeed * Up;
|
||||
Position -= d;
|
||||
Direction -= d;
|
||||
}
|
||||
|
||||
if (keyboard.IsKeyDown(Keys.C)) // zoom in
|
||||
ModifyZoom(+.5f);
|
||||
if (keyboard.IsKeyDown(Keys.X)) // zoom out
|
||||
ModifyZoom(-.5f);
|
||||
}
|
||||
|
||||
private void ModifyZoom(float zoomAmount)
|
||||
{
|
||||
//We don't want to be able to zoom in too close or too far away so clamp to these values
|
||||
Zoom = Math.Clamp(Zoom - zoomAmount, 1.0f, 89f);
|
||||
}
|
||||
|
||||
public 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 GetViewMatrix() => Matrix4x4.CreateLookAt(Position, Direction, Up);
|
||||
public Matrix4x4 GetProjectionMatrix()
|
||||
{
|
||||
return Matrix4x4.CreatePerspectiveFieldOfView(Helper.DegreesToRadians(Zoom), AspectRatio, Near, Far);
|
||||
}
|
||||
=> 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
|
||||
|
|
@ -84,13 +172,18 @@ public class Camera
|
|||
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));
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, new Vector2(8, 3));
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.CellPadding, new Vector2(0, 1));
|
||||
if (ImGui.BeginTable("camera_editor", 2))
|
||||
{
|
||||
SnimGui.Layout("Speed");ImGui.PushID(1);
|
||||
SnimGui.Layout("Mode");
|
||||
ImGui.PushID(1);var m = (int) Mode;
|
||||
ImGui.Combo("world_mode", ref m, "Fly Cam\0Arcball\0");
|
||||
Mode = (WorldMode) m;ImGui.PopID();
|
||||
|
||||
SnimGui.Layout("Speed");ImGui.PushID(2);
|
||||
ImGui.DragFloat("", ref Speed, _step, _zero, _infinite, "%.2f m/s", _clamp);
|
||||
ImGui.PopID();SnimGui.Layout("Far Plane");ImGui.PushID(2);
|
||||
ImGui.PopID();SnimGui.Layout("Far Plane");ImGui.PushID(3);
|
||||
ImGui.DragFloat("", ref Far, 0.1f, 0.1f, Far * 2f, "%.2f m", _clamp);
|
||||
ImGui.PopID();
|
||||
|
||||
|
|
|
|||
|
|
@ -30,20 +30,37 @@ public abstract class Light : IDisposable
|
|||
};
|
||||
public readonly FGuid Model;
|
||||
public readonly Texture Icon;
|
||||
public readonly Transform Transform;
|
||||
public Transform Transform;
|
||||
|
||||
public Vector4 Color;
|
||||
public float Intensity;
|
||||
public bool IsSetup;
|
||||
|
||||
public Light(FGuid model, Texture icon, UObject parent, UObject light, FVector position)
|
||||
public Light(Texture icon, UObject light)
|
||||
{
|
||||
var p = light.GetOrDefault("RelativeLocation", parent.GetOrDefault("RelativeLocation", FVector.ZeroVector));
|
||||
var r = light.GetOrDefault("RelativeRotation", parent.GetOrDefault("RelativeRotation", FRotator.ZeroRotator));
|
||||
Transform = new Transform
|
||||
{
|
||||
Position = light.GetOrDefault("RelativeLocation", FVector.ZeroVector) * Constants.SCALE_DOWN_RATIO,
|
||||
Rotation = light.GetOrDefault("RelativeRotation", FRotator.ZeroRotator).Quaternion(),
|
||||
Scale = light.GetOrDefault("RelativeScale3D", FVector.OneVector)
|
||||
};
|
||||
|
||||
Transform = Transform.Identity;
|
||||
Transform.Scale = new FVector(0.2f);
|
||||
Transform.Position = position + r.RotateVector(p.ToMapVector()) * Constants.SCALE_DOWN_RATIO;
|
||||
Model = new FGuid((uint) light.GetFullName().GetHashCode());
|
||||
Icon = icon;
|
||||
|
||||
Color = light.GetOrDefault("LightColor", new FColor(0xFF, 0xFF, 0xFF, 0xFF));
|
||||
Intensity = light.GetOrDefault("Intensity", 1.0f);
|
||||
}
|
||||
|
||||
public Light(FGuid model, Texture icon, UObject parent, UObject light, Transform transform)
|
||||
{
|
||||
Transform = new Transform
|
||||
{
|
||||
Relation = transform.Matrix,
|
||||
Position = light.GetOrDefault("RelativeLocation", parent.GetOrDefault("RelativeLocation", FVector.ZeroVector)) * Constants.SCALE_DOWN_RATIO,
|
||||
Rotation = light.GetOrDefault("RelativeRotation", parent.GetOrDefault("RelativeRotation", FRotator.ZeroRotator)).Quaternion(),
|
||||
Scale = light.GetOrDefault("RelativeScale3D", parent.GetOrDefault("RelativeScale3D", FVector.OneVector))
|
||||
};
|
||||
|
||||
Model = model;
|
||||
Icon = icon;
|
||||
|
|
@ -59,6 +76,13 @@ public abstract class Light : IDisposable
|
|||
_vao.BindInstancing(); // VertexAttributePointer
|
||||
}
|
||||
|
||||
public void UpdateMatrices()
|
||||
{
|
||||
_matrixVbo.Bind();
|
||||
_matrixVbo.Update(0, Transform.Matrix);
|
||||
_matrixVbo.Unbind();
|
||||
}
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
_handle = GL.CreateProgram();
|
||||
|
|
@ -75,6 +99,8 @@ public abstract class Light : IDisposable
|
|||
|
||||
public void Render(Shader shader)
|
||||
{
|
||||
GL.Disable(EnableCap.CullFace);
|
||||
|
||||
_vao.Bind();
|
||||
|
||||
Icon?.Bind(TextureUnit.Texture0);
|
||||
|
|
@ -82,12 +108,14 @@ public abstract class Light : IDisposable
|
|||
shader.SetUniform("uColor", Color);
|
||||
|
||||
GL.DrawArrays(PrimitiveType.Triangles, 0, Indices.Length);
|
||||
|
||||
GL.Enable(EnableCap.CullFace);
|
||||
}
|
||||
|
||||
public virtual void Render(int i, Shader shader)
|
||||
{
|
||||
shader.SetUniform($"uLights[{i}].Base.Color", Color);
|
||||
shader.SetUniform($"uLights[{i}].Base.Position", Transform.Position);
|
||||
shader.SetUniform($"uLights[{i}].Base.Position", Transform.Matrix.Translation);
|
||||
shader.SetUniform($"uLights[{i}].Base.Intensity", Intensity);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using FModel.Views.Snooper.Shading;
|
||||
using ImGuiNET;
|
||||
|
|
@ -12,7 +11,17 @@ 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)
|
||||
public PointLight(Texture icon, UObject point) : base(icon, point)
|
||||
{
|
||||
if (!point.TryGetValue(out float radius, "SourceRadius", "AttenuationRadius"))
|
||||
radius = 1.0f;
|
||||
|
||||
radius *= Constants.SCALE_DOWN_RATIO;
|
||||
Linear = 4.5f / radius;
|
||||
Quadratic = 75.0f / MathF.Pow(radius, 2.0f);
|
||||
}
|
||||
|
||||
public PointLight(FGuid model, Texture icon, UObject parent, UObject point, Transform transform) : base(model, icon, parent, point, transform)
|
||||
{
|
||||
if (!point.TryGetValue(out float radius, "AttenuationRadius", "SourceRadius"))
|
||||
radius = 1.0f;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using FModel.Views.Snooper.Shading;
|
||||
using ImGuiNET;
|
||||
|
|
@ -12,14 +11,28 @@ public class SpotLight : Light
|
|||
public float InnerConeAngle;
|
||||
public float OuterConeAngle;
|
||||
|
||||
public SpotLight(FGuid model, Texture icon, UObject parent, UObject spot, FVector position) : base(model, icon, parent, spot, position)
|
||||
public SpotLight(Texture icon, UObject spot) : base(icon, spot)
|
||||
{
|
||||
if (!spot.TryGetValue(out Attenuation, "SourceRadius", "AttenuationRadius"))
|
||||
Attenuation = 1.0f;
|
||||
|
||||
Attenuation *= Constants.SCALE_DOWN_RATIO;
|
||||
InnerConeAngle = spot.GetOrDefault("InnerConeAngle", 50.0f);
|
||||
OuterConeAngle = spot.GetOrDefault("OuterConeAngle", InnerConeAngle + 10);
|
||||
if (OuterConeAngle < InnerConeAngle)
|
||||
InnerConeAngle = OuterConeAngle - 10;
|
||||
}
|
||||
|
||||
public SpotLight(FGuid model, Texture icon, UObject parent, UObject spot, Transform transform) : base(model, icon, parent, spot, transform)
|
||||
{
|
||||
if (!spot.TryGetValue(out Attenuation, "AttenuationRadius", "SourceRadius"))
|
||||
Attenuation = 1.0f;
|
||||
|
||||
Attenuation *= Constants.SCALE_DOWN_RATIO;
|
||||
InnerConeAngle = spot.GetOrDefault("InnerConeAngle", 50.0f);
|
||||
OuterConeAngle = spot.GetOrDefault("OuterConeAngle", 60.0f);
|
||||
OuterConeAngle = spot.GetOrDefault("OuterConeAngle", InnerConeAngle + 10);
|
||||
if (OuterConeAngle < InnerConeAngle)
|
||||
InnerConeAngle = OuterConeAngle - 10;
|
||||
}
|
||||
|
||||
public override void Render(int i, Shader shader)
|
||||
|
|
|
|||
|
|
@ -1,64 +0,0 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using CUE4Parse_Conversion.Animations;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
|
||||
namespace FModel.Views.Snooper.Models.Animations;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
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;
|
||||
using FModel.Views.Snooper.Shading;
|
||||
|
||||
namespace FModel.Views.Snooper.Models.Animations;
|
||||
|
||||
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;
|
||||
var matrix = Matrix4x4.Identity;
|
||||
while (boneIndex > -1)
|
||||
{
|
||||
var bone = RefSkel.ReferenceSkeleton.FinalRefBonePose[boneIndex];
|
||||
boneIndex = RefSkel.ReferenceSkeleton.FinalRefBoneInfo[boneIndex].ParentIndex;
|
||||
var parentBone = RefSkel.ReferenceSkeleton.FinalRefBonePose[boneIndex < 0 ? 0 : boneIndex];
|
||||
|
||||
var orig_loc = bone.Translation;
|
||||
parentBone.Rotation.Conjugate();
|
||||
orig_loc = parentBone.Rotation.RotateVector(orig_loc);
|
||||
|
||||
var orig_quat = bone.Rotation;
|
||||
orig_quat *= parentBone.Rotation;
|
||||
orig_quat.Conjugate();
|
||||
|
||||
var p_rotated = orig_quat * orig_loc;
|
||||
orig_quat.Conjugate();
|
||||
p_rotated *= orig_quat;
|
||||
|
||||
matrix *=
|
||||
Matrix4x4.CreateFromQuaternion(orig_quat) *
|
||||
Matrix4x4.CreateTranslation(p_rotated);
|
||||
|
||||
// Console.WriteLine(matrix.Translation);
|
||||
}
|
||||
// for (int j = 0; j <= boneIndex; j++)
|
||||
// {
|
||||
// var t = RefSkel.ReferenceSkeleton.FinalRefBonePose[j];
|
||||
// var r = RefSkel.ReferenceSkeleton.FinalRefBonePose[j - (j == 0 ? 0 : 1)].Rotation;
|
||||
// r.Conjugate();
|
||||
// matrix *= Matrix4x4.CreateFromQuaternion(r) * Matrix4x4.CreateTranslation(t.Translation);
|
||||
//
|
||||
// Console.WriteLine($@"{t.Translation}");
|
||||
// transform.Relation *= matrix;
|
||||
// }
|
||||
|
||||
Sockets[i] = new Socket(socket, matrix.Translation);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,66 +1,41 @@
|
|||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using CUE4Parse_Conversion.Meshes.PSK;
|
||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using FModel.Views.Snooper.Shading;
|
||||
|
||||
namespace FModel.Views.Snooper.Models;
|
||||
|
||||
public class Cube : Model
|
||||
{
|
||||
public Cube(UMaterialInterface unrealMaterial) : base(unrealMaterial)
|
||||
public Cube(CStaticMesh mesh, UMaterialInterface unrealMaterial) : base(unrealMaterial)
|
||||
{
|
||||
Indices = new uint[]
|
||||
var lod = mesh.LODs[0];
|
||||
|
||||
Indices = new uint[lod.Indices.Value.Length];
|
||||
for (int i = 0; i < Indices.Length; i++)
|
||||
{
|
||||
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,
|
||||
Indices[i] = (uint) lod.Indices.Value[i];
|
||||
}
|
||||
|
||||
-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
|
||||
};
|
||||
Vertices = new float[lod.NumVerts * VertexSize];
|
||||
for (int i = 0; i < lod.Verts.Length; i++)
|
||||
{
|
||||
var count = 0;
|
||||
var baseIndex = i * VertexSize;
|
||||
var vert = lod.Verts[i];
|
||||
Vertices[baseIndex + count++] = i;
|
||||
Vertices[baseIndex + count++] = vert.Position.X * Constants.SCALE_DOWN_RATIO;
|
||||
Vertices[baseIndex + count++] = vert.Position.Z * Constants.SCALE_DOWN_RATIO;
|
||||
Vertices[baseIndex + count++] = vert.Position.Y * Constants.SCALE_DOWN_RATIO;
|
||||
Vertices[baseIndex + count++] = vert.Normal.X;
|
||||
Vertices[baseIndex + count++] = vert.Normal.Z;
|
||||
Vertices[baseIndex + count++] = vert.Normal.Y;
|
||||
Vertices[baseIndex + count++] = vert.Tangent.X;
|
||||
Vertices[baseIndex + count++] = vert.Tangent.Z;
|
||||
Vertices[baseIndex + count++] = vert.Tangent.Y;
|
||||
Vertices[baseIndex + count++] = vert.UV.U;
|
||||
Vertices[baseIndex + count++] = vert.UV.V;
|
||||
Vertices[baseIndex + count++] = .5f;
|
||||
}
|
||||
|
||||
Materials = new Material[1];
|
||||
Materials[0] = new Material(unrealMaterial) { IsUsed = true };
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ public class Grid : IDisposable
|
|||
|
||||
public void Render(Matrix4x4 viewMatrix, Matrix4x4 projMatrix, float near, float far)
|
||||
{
|
||||
GL.Disable(EnableCap.CullFace);
|
||||
GL.Disable(EnableCap.DepthTest);
|
||||
_vao.Bind();
|
||||
|
||||
|
|
@ -55,6 +56,7 @@ public class Grid : IDisposable
|
|||
|
||||
GL.DrawArrays(PrimitiveType.Triangles, 0, Indices.Length);
|
||||
GL.Enable(EnableCap.DepthTest);
|
||||
GL.Enable(EnableCap.CullFace);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
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;
|
||||
|
|
@ -11,19 +10,38 @@ using CUE4Parse.UE4.Assets.Exports;
|
|||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
|
||||
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using FModel.Extensions;
|
||||
using FModel.Services;
|
||||
using FModel.Settings;
|
||||
using FModel.Views.Snooper.Animations;
|
||||
using FModel.Views.Snooper.Buffers;
|
||||
using FModel.Views.Snooper.Models.Animations;
|
||||
using FModel.Views.Snooper.Shading;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
|
||||
namespace FModel.Views.Snooper.Models;
|
||||
|
||||
public class VertexAttribute
|
||||
{
|
||||
public int Size;
|
||||
public bool Enabled;
|
||||
}
|
||||
|
||||
public enum EAttribute
|
||||
{
|
||||
Index,
|
||||
Position,
|
||||
Normals,
|
||||
Tangent,
|
||||
UVs,
|
||||
Layer,
|
||||
Colors,
|
||||
BonesId,
|
||||
BonesWeight
|
||||
}
|
||||
|
||||
public class Model : IDisposable
|
||||
{
|
||||
private int _handle;
|
||||
private const int _LOD_INDEX = 0;
|
||||
|
||||
private BufferObject<uint> _ebo;
|
||||
private BufferObject<float> _vbo;
|
||||
|
|
@ -31,90 +49,137 @@ public class Model : IDisposable
|
|||
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 List<VertexAttribute> _vertexAttributes = new()
|
||||
{
|
||||
new VertexAttribute { Size = 1, Enabled = true }, // VertexIndex
|
||||
new VertexAttribute { Size = 3, Enabled = true }, // Position
|
||||
new VertexAttribute { Size = 3, Enabled = true }, // Normal
|
||||
new VertexAttribute { Size = 3, Enabled = true }, // Tangent
|
||||
new VertexAttribute { Size = 2, Enabled = true }, // UV
|
||||
new VertexAttribute { Size = 1, Enabled = true }, // TextureLayer
|
||||
new VertexAttribute { Size = 4, Enabled = false }, // Colors
|
||||
new VertexAttribute { Size = 4, Enabled = false }, // BoneIds
|
||||
new VertexAttribute { Size = 4, Enabled = false } // BoneWeights
|
||||
};
|
||||
public int VertexSize => _vertexAttributes.Where(x => x.Enabled).Sum(x => x.Size);
|
||||
public bool HasVertexColors => _vertexAttributes[(int) EAttribute.Colors].Enabled;
|
||||
private const int _faceSize = 3;
|
||||
|
||||
public readonly UObject Export;
|
||||
public readonly string Path;
|
||||
public readonly string Name;
|
||||
public readonly string Type;
|
||||
public readonly bool HasVertexColors;
|
||||
public readonly bool HasMorphTargets;
|
||||
public readonly int UvCount;
|
||||
public readonly FBox Box;
|
||||
public uint[] Indices;
|
||||
public float[] Vertices;
|
||||
public Section[] Sections;
|
||||
public Material[] Materials;
|
||||
public bool TwoSided;
|
||||
public bool IsAnimatedProp;
|
||||
|
||||
public bool HasSkeleton => Skeleton is { IsLoaded: true };
|
||||
public bool HasSkeleton => Skeleton != null;
|
||||
public readonly Skeleton Skeleton;
|
||||
|
||||
public bool HasSockets => Sockets.Count > 0;
|
||||
public readonly List<Socket> Sockets;
|
||||
|
||||
public bool HasMorphTargets => Morphs.Count > 0;
|
||||
public readonly List<Morph> Morphs;
|
||||
|
||||
private string _attachedTo = string.Empty;
|
||||
private readonly List<string> _attachedFor = new ();
|
||||
public bool IsAttached => _attachedTo.Length > 0;
|
||||
public bool IsAttachment => _attachedFor.Count > 0;
|
||||
public string AttachIcon => IsAttachment ? "link_has" : IsAttached ? "link_on" : "link_off";
|
||||
public string AttachTooltip => IsAttachment ? $"Is Attachment For:\n{string.Join("\n", _attachedFor)}" : IsAttached ? $"Is Attached To {_attachedTo}" : "Not Attached To Any Socket Nor Attachment For Any Model";
|
||||
|
||||
public int TransformsCount;
|
||||
public readonly List<Transform> Transforms;
|
||||
|
||||
public readonly Morph[] Morphs;
|
||||
private Matrix4x4 _previousMatrix;
|
||||
|
||||
public bool Show;
|
||||
public bool Wireframe;
|
||||
public bool IsSetup;
|
||||
public bool IsSetup { get; private set; }
|
||||
public bool IsSelected;
|
||||
public int SelectedInstance;
|
||||
public float MorphTime;
|
||||
|
||||
protected Model(UObject export)
|
||||
{
|
||||
_export = export;
|
||||
Path = _export.GetPathName();
|
||||
Export = export;
|
||||
Path = Export.GetPathName();
|
||||
Name = Path.SubstringAfterLast('/').SubstringBefore('.');
|
||||
Type = export.ExportType;
|
||||
UvCount = 1;
|
||||
Box = new FBox(new FVector(-2f), new FVector(2f));
|
||||
Sockets = new List<Socket>();
|
||||
Morphs = new List<Morph>();
|
||||
Transforms = new List<Transform>();
|
||||
}
|
||||
|
||||
public Model(UStaticMesh export, CStaticMesh staticMesh) : this(export, staticMesh, Transform.Identity) {}
|
||||
public Model(UStaticMesh export, CStaticMesh staticMesh, Transform transform) : this(export, export.Materials, null, staticMesh.LODs.Count, staticMesh.LODs[0], staticMesh.LODs[0].Verts, transform) {}
|
||||
private Model(USkeletalMesh export, CSkeletalMesh skeletalMesh, Transform transform) : this(export, export.Materials, export.Skeleton, skeletalMesh.LODs.Count, skeletalMesh.LODs[0], skeletalMesh.LODs[0].Verts, transform) {}
|
||||
public Model(USkeletalMesh export, CSkeletalMesh skeletalMesh) : this(export, skeletalMesh, Transform.Identity)
|
||||
public Model(UStaticMesh export, CStaticMesh staticMesh, Transform transform) : this(export, export.Materials, staticMesh.LODs, transform)
|
||||
{
|
||||
var morphTargets = export.MorphTargets;
|
||||
if (morphTargets is not { Length: > 0 })
|
||||
return;
|
||||
Box = staticMesh.BoundingBox * Constants.SCALE_DOWN_RATIO;
|
||||
|
||||
var length = morphTargets.Length;
|
||||
|
||||
HasMorphTargets = true;
|
||||
Morphs = new Morph[length];
|
||||
for (var i = 0; i < Morphs.Length; i++)
|
||||
for (int i = 0; i < export.Sockets.Length; i++)
|
||||
{
|
||||
Morphs[i] = new Morph(Vertices, _vertexSize, morphTargets[i].Load<UMorphTarget>());
|
||||
ApplicationService.ApplicationView.Status.UpdateStatusLabel($"{Morphs[i].Name} ... {i}/{length}");
|
||||
if (export.Sockets[i].Load<UStaticMeshSocket>() is not { } socket) continue;
|
||||
Sockets.Add(new Socket(socket));
|
||||
}
|
||||
ApplicationService.ApplicationView.Status.UpdateStatusLabel("");
|
||||
}
|
||||
|
||||
private Model(UObject export, ResolvedObject[] materials, FPackageIndex skeleton, int numLods, CBaseMeshLod lod, CMeshVertex[] vertices, Transform transform = null) : this(export)
|
||||
public Model(USkeletalMesh export, CSkeletalMesh skeletalMesh) : this(export, skeletalMesh, Transform.Identity) {}
|
||||
public Model(USkeletalMesh export, CSkeletalMesh skeletalMesh, Transform transform) : this(export, export.Materials, skeletalMesh.LODs, transform)
|
||||
{
|
||||
Box = skeletalMesh.BoundingBox * Constants.SCALE_DOWN_RATIO;
|
||||
Skeleton = new Skeleton(export.ReferenceSkeleton);
|
||||
|
||||
var sockets = new List<FPackageIndex>();
|
||||
sockets.AddRange(export.Sockets);
|
||||
if (HasSkeleton && export.Skeleton.TryLoad(out USkeleton skeleton))
|
||||
{
|
||||
Skeleton.Name = skeleton.Name;
|
||||
sockets.AddRange(skeleton.Sockets);
|
||||
}
|
||||
|
||||
for (int i = 0; i < sockets.Count; i++)
|
||||
{
|
||||
if (sockets[i].Load<USkeletalMeshSocket>() is not { } socket) continue;
|
||||
Sockets.Add(new Socket(socket));
|
||||
}
|
||||
|
||||
for (var i = 0; i < export.MorphTargets.Length; i++)
|
||||
{
|
||||
if (!export.MorphTargets[i].TryLoad(out UMorphTarget morphTarget) ||
|
||||
morphTarget.MorphLODModels.Length < 1 || morphTarget.MorphLODModels[0].Vertices.Length < 1)
|
||||
continue;
|
||||
|
||||
Morphs.Add(new Morph(Vertices, VertexSize, morphTarget));
|
||||
}
|
||||
}
|
||||
|
||||
private Model(UObject export, IReadOnlyList<ResolvedObject> materials, IReadOnlyList<CStaticMeshLod> lods, Transform transform = null)
|
||||
: this(export, materials, lods[_LOD_INDEX], lods[_LOD_INDEX].Verts, lods.Count, transform) {}
|
||||
private Model(UObject export, IReadOnlyList<ResolvedObject> materials, IReadOnlyList<CSkelMeshLod> lods, Transform transform = null)
|
||||
: this(export, materials, lods[_LOD_INDEX], lods[_LOD_INDEX].Verts, lods.Count, transform) {}
|
||||
private Model(UObject export, IReadOnlyList<ResolvedObject> materials, CBaseMeshLod lod, IReadOnlyList<CMeshVertex> vertices, int numLods, Transform transform = null) : this(export)
|
||||
{
|
||||
var hasCustomUvs = lod.ExtraUV.IsValueCreated;
|
||||
UvCount = hasCustomUvs ? Math.Max(lod.NumTexCoords, numLods) : lod.NumTexCoords;
|
||||
TwoSided = lod.IsTwoSided;
|
||||
|
||||
Materials = new Material[materials.Length];
|
||||
Materials = new Material[materials.Count];
|
||||
for (int m = 0; m < Materials.Length; m++)
|
||||
{
|
||||
if ((materials[m]?.TryLoad(out var material) ?? false) && material is UMaterialInterface unrealMaterial)
|
||||
Materials[m] = new Material(unrealMaterial); else Materials[m] = new Material();
|
||||
}
|
||||
|
||||
if (lod.VertexColors is { Length: > 0})
|
||||
{
|
||||
HasVertexColors = true;
|
||||
_vertexSize += 4; // + Color
|
||||
}
|
||||
|
||||
if (skeleton != null)
|
||||
{
|
||||
Skeleton = new Skeleton(skeleton);
|
||||
_vertexSize += 8; // + BoneIds + BoneWeights
|
||||
}
|
||||
_vertexAttributes[(int) EAttribute.Colors].Enabled = lod.VertexColors is { Length: > 0};
|
||||
_vertexAttributes[(int) EAttribute.BonesId].Enabled =
|
||||
_vertexAttributes[(int) EAttribute.BonesWeight].Enabled = vertices is CSkelMeshVertex[];
|
||||
|
||||
Indices = new uint[lod.Indices.Value.Length];
|
||||
for (int i = 0; i < Indices.Length; i++)
|
||||
|
|
@ -122,11 +187,11 @@ public class Model : IDisposable
|
|||
Indices[i] = (uint) lod.Indices.Value[i];
|
||||
}
|
||||
|
||||
Vertices = new float[lod.NumVerts * _vertexSize];
|
||||
for (int i = 0; i < vertices.Length; i++)
|
||||
Vertices = new float[lod.NumVerts * VertexSize];
|
||||
for (int i = 0; i < vertices.Count; i++)
|
||||
{
|
||||
var count = 0;
|
||||
var baseIndex = i * _vertexSize;
|
||||
var baseIndex = i * VertexSize;
|
||||
var vert = vertices[i];
|
||||
Vertices[baseIndex + count++] = i;
|
||||
Vertices[baseIndex + count++] = vert.Position.X * Constants.SCALE_DOWN_RATIO;
|
||||
|
|
@ -151,9 +216,8 @@ public class Model : IDisposable
|
|||
Vertices[baseIndex + count++] = color.A;
|
||||
}
|
||||
|
||||
if (HasSkeleton)
|
||||
if (vert is CSkelMeshVertex skelVert)
|
||||
{
|
||||
var skelVert = (CSkelMeshVertex) vert;
|
||||
var weightsHash = skelVert.UnpackWeights();
|
||||
Vertices[baseIndex + count++] = skelVert.Bone[0];
|
||||
Vertices[baseIndex + count++] = skelVert.Bone[1];
|
||||
|
|
@ -170,23 +234,53 @@ public class Model : IDisposable
|
|||
for (var s = 0; s < Sections.Length; s++)
|
||||
{
|
||||
var section = lod.Sections.Value[s];
|
||||
Sections[s] = new Section(section.MaterialIndex, section.NumFaces * _faceSize, section.FirstIndex, Materials[section.MaterialIndex]);
|
||||
Sections[s] = new Section(section.MaterialIndex, section.NumFaces * _faceSize, section.FirstIndex);
|
||||
if (section.IsValid) Sections[s].SetupMaterial(Materials[section.MaterialIndex]);
|
||||
}
|
||||
|
||||
AddInstance(transform ?? Transform.Identity);
|
||||
var t = transform ?? Transform.Identity;
|
||||
_previousMatrix = t.Matrix;
|
||||
AddInstance(t);
|
||||
}
|
||||
|
||||
public void AddInstance(Transform transform)
|
||||
{
|
||||
SelectedInstance = TransformsCount;
|
||||
TransformsCount++;
|
||||
Transforms.Add(transform);
|
||||
}
|
||||
|
||||
public void UpdateMatrix(int instance)
|
||||
public void UpdateMatrices(Options options)
|
||||
{
|
||||
var worldMatrix = UpdateMatrices();
|
||||
foreach (var socket in Sockets)
|
||||
{
|
||||
var boneMatrix = Matrix4x4.Identity;
|
||||
if (HasSkeleton && Skeleton.BonesByLoweredName.TryGetValue(socket.BoneName.Text.ToLower(), out var bone))
|
||||
boneMatrix = Skeleton.GetBoneMatrix(bone);
|
||||
|
||||
var socketRelation = boneMatrix * worldMatrix;
|
||||
foreach (var info in socket.AttachedModels)
|
||||
{
|
||||
if (!options.TryGetModel(info.Guid, out var attachedModel))
|
||||
continue;
|
||||
|
||||
attachedModel.Transforms[info.Instance].Relation = socket.Transform.LocalMatrix * socketRelation;
|
||||
attachedModel.UpdateMatrices(options);
|
||||
}
|
||||
}
|
||||
}
|
||||
private Matrix4x4 UpdateMatrices()
|
||||
{
|
||||
_matrixVbo.Bind();
|
||||
_matrixVbo.Update(instance, Transforms[instance].Matrix);
|
||||
for (int instance = 0; instance < TransformsCount; instance++)
|
||||
{
|
||||
var matrix = Transforms[instance].Matrix;
|
||||
_matrixVbo.Update(instance, matrix);
|
||||
_previousMatrix = matrix;
|
||||
}
|
||||
_matrixVbo.Unbind();
|
||||
return _previousMatrix;
|
||||
}
|
||||
|
||||
public void UpdateMorph(int index)
|
||||
|
|
@ -196,6 +290,31 @@ public class Model : IDisposable
|
|||
_morphVbo.Unbind();
|
||||
}
|
||||
|
||||
public void AttachModel(Model attachedTo, Socket socket, SocketAttachementInfo info)
|
||||
{
|
||||
socket.AttachedModels.Add(info);
|
||||
|
||||
_attachedTo = $"'{socket.Name}' from '{attachedTo.Name}'{(!socket.BoneName.IsNone ? $" at '{socket.BoneName}'" : "")}";
|
||||
attachedTo._attachedFor.Add($"'{Name}'");
|
||||
// reset PRS to 0 so it's attached to the actual position (can be transformed relative to the socket later by the user)
|
||||
Transforms[SelectedInstance].Position = FVector.ZeroVector;
|
||||
Transforms[SelectedInstance].Rotation = FQuat.Identity;
|
||||
Transforms[SelectedInstance].Scale = FVector.OneVector;
|
||||
}
|
||||
|
||||
public void DetachModel(Model attachedTo, Socket socket, SocketAttachementInfo info)
|
||||
{
|
||||
socket.AttachedModels.Remove(info);
|
||||
SafeDetachModel(attachedTo);
|
||||
}
|
||||
|
||||
public void SafeDetachModel(Model attachedTo)
|
||||
{
|
||||
_attachedTo = string.Empty;
|
||||
attachedTo._attachedFor.Remove($"'{Name}'");
|
||||
Transforms[SelectedInstance].Relation = _previousMatrix;
|
||||
}
|
||||
|
||||
public void SetupInstances()
|
||||
{
|
||||
var instanceMatrix = new Matrix4x4[TransformsCount];
|
||||
|
|
@ -208,21 +327,24 @@ public class Model : IDisposable
|
|||
public void Setup(Options options)
|
||||
{
|
||||
_handle = GL.CreateProgram();
|
||||
var broken = GL.GetInteger(GetPName.MaxTextureUnits) == 0;
|
||||
var broken = GL.GetInteger(GetPName.MaxTextureCoords) == 0;
|
||||
|
||||
_ebo = new BufferObject<uint>(Indices, BufferTarget.ElementArrayBuffer);
|
||||
_vbo = new BufferObject<float>(Vertices, BufferTarget.ArrayBuffer);
|
||||
_vao = new VertexArrayObject<float, uint>(_vbo, _ebo);
|
||||
|
||||
_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
|
||||
if (!broken) _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
|
||||
var offset = 0;
|
||||
for (int i = 0; i < _vertexAttributes.Count; i++)
|
||||
{
|
||||
var attribute = _vertexAttributes[i];
|
||||
if (!attribute.Enabled) continue;
|
||||
|
||||
if (i != 5 || !broken)
|
||||
{
|
||||
_vao.VertexAttributePointer((uint) i, attribute.Size, i == 0 ? VertexAttribPointerType.Int : VertexAttribPointerType.Float, VertexSize, offset);
|
||||
}
|
||||
offset += attribute.Size;
|
||||
}
|
||||
|
||||
SetupInstances(); // instanced models transform
|
||||
|
||||
|
|
@ -233,46 +355,58 @@ public class Model : IDisposable
|
|||
Materials[i].Setup(options, broken ? 1 : UvCount);
|
||||
}
|
||||
|
||||
if (HasSkeleton) Skeleton.Setup();
|
||||
if (HasMorphTargets)
|
||||
{
|
||||
for (uint morph = 0; morph < Morphs.Length; morph++)
|
||||
for (int morph = 0; morph < Morphs.Count; morph++)
|
||||
{
|
||||
Morphs[morph].Setup();
|
||||
if (morph == 0)
|
||||
_morphVbo = new BufferObject<float>(Morphs[morph].Vertices, BufferTarget.ArrayBuffer);
|
||||
}
|
||||
_vao.Bind();
|
||||
_vao.VertexAttributePointer(13, 3, VertexAttribPointerType.Float, 3, 0); // morph position
|
||||
_vao.VertexAttributePointer(13, 3, VertexAttribPointerType.Float, Morph.VertexSize, 0); // morph position
|
||||
_vao.VertexAttributePointer(14, 3, VertexAttribPointerType.Float, Morph.VertexSize, 0); // morph tangent
|
||||
_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)
|
||||
public void Render(Shader shader, bool outline = false)
|
||||
{
|
||||
if (outline) GL.Disable(EnableCap.DepthTest);
|
||||
if (TwoSided) GL.Disable(EnableCap.CullFace);
|
||||
if (IsSelected)
|
||||
{
|
||||
GL.Enable(EnableCap.StencilTest);
|
||||
GL.StencilFunc(StencilFunction.Always, 1, 0xFF);
|
||||
GL.StencilFunc(outline ? StencilFunction.Notequal : StencilFunction.Always, 1, 0xFF);
|
||||
}
|
||||
|
||||
_vao.Bind();
|
||||
shader.SetUniform("uMorphTime", MorphTime);
|
||||
shader.SetUniform("uUvCount", UvCount);
|
||||
shader.SetUniform("uHasVertexColors", HasVertexColors);
|
||||
if (HasSkeleton) Skeleton.Render();
|
||||
if (!outline)
|
||||
{
|
||||
shader.SetUniform("uUvCount", UvCount);
|
||||
shader.SetUniform("uHasVertexColors", HasVertexColors);
|
||||
}
|
||||
|
||||
GL.PolygonMode(MaterialFace.FrontAndBack, Wireframe ? PolygonMode.Line : PolygonMode.Fill);
|
||||
foreach (var section in Sections)
|
||||
{
|
||||
if (!section.Show) continue;
|
||||
Materials[section.MaterialIndex].Render(shader);
|
||||
if (!outline)
|
||||
{
|
||||
shader.SetUniform("uSectionColor", section.Color);
|
||||
Materials[section.MaterialIndex].Render(shader);
|
||||
}
|
||||
|
||||
GL.DrawElementsInstanced(PrimitiveType.Triangles, section.FacesCount, DrawElementsType.UnsignedInt, section.FirstFaceIndexPtr, TransformsCount);
|
||||
}
|
||||
_vao.Unbind();
|
||||
|
|
@ -282,56 +416,26 @@ public class Model : IDisposable
|
|||
GL.StencilFunc(StencilFunction.Always, 0, 0xFF);
|
||||
GL.Disable(EnableCap.StencilTest);
|
||||
}
|
||||
if (TwoSided) GL.Enable(EnableCap.CullFace);
|
||||
if (outline) GL.Enable(EnableCap.DepthTest);
|
||||
}
|
||||
|
||||
public void SimpleRender(Shader shader)
|
||||
public void PickingRender(Shader shader)
|
||||
{
|
||||
if (TwoSided) GL.Disable(EnableCap.CullFace);
|
||||
|
||||
_vao.Bind();
|
||||
shader.SetUniform("uMorphTime", MorphTime);
|
||||
if (HasSkeleton) Skeleton.Render();
|
||||
|
||||
foreach (var section in Sections)
|
||||
{
|
||||
if (!section.Show) continue;
|
||||
GL.DrawElementsInstanced(PrimitiveType.Triangles, section.FacesCount, DrawElementsType.UnsignedInt, section.FirstFaceIndexPtr, 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);
|
||||
foreach (var section in Sections)
|
||||
{
|
||||
if (!section.Show) continue;
|
||||
GL.DrawElementsInstancedBaseInstance(PrimitiveType.Triangles, section.FacesCount, DrawElementsType.UnsignedInt, section.FirstFaceIndexPtr, 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
|
||||
{
|
||||
LodFormat = UserSettings.Default.LodExportFormat,
|
||||
MeshFormat = UserSettings.Default.MeshExportFormat,
|
||||
MaterialFormat = UserSettings.Default.MaterialExportFormat,
|
||||
TextureFormat = UserSettings.Default.TextureExportFormat,
|
||||
SocketFormat = UserSettings.Default.SocketExportFormat,
|
||||
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);
|
||||
if (TwoSided) GL.Enable(EnableCap.CullFace);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
@ -340,19 +444,18 @@ public class Model : IDisposable
|
|||
_vbo.Dispose();
|
||||
_matrixVbo.Dispose();
|
||||
_vao.Dispose();
|
||||
if (HasMorphTargets)
|
||||
Skeleton?.Dispose();
|
||||
for (int socket = 0; socket < Sockets.Count; socket++)
|
||||
{
|
||||
_morphVbo.Dispose();
|
||||
for (var morph = 0; morph < Morphs.Length; morph++)
|
||||
{
|
||||
Morphs[morph].Dispose();
|
||||
}
|
||||
Sockets[socket]?.Dispose();
|
||||
}
|
||||
|
||||
for (int section = 0; section < Sections.Length; section++)
|
||||
Sockets.Clear();
|
||||
if (HasMorphTargets) _morphVbo.Dispose();
|
||||
for (var morph = 0; morph < Morphs.Count; morph++)
|
||||
{
|
||||
Sections[section].Dispose();
|
||||
Morphs[morph]?.Dispose();
|
||||
}
|
||||
Morphs.Clear();
|
||||
|
||||
GL.DeleteProgram(_handle);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ public class Morph : IDisposable
|
|||
{
|
||||
private int _handle;
|
||||
|
||||
private readonly int _vertexSize = 3; // Position
|
||||
public static readonly int VertexSize = 6; // Position + Tangent
|
||||
|
||||
public readonly string Name;
|
||||
public readonly float[] Vertices;
|
||||
|
|
@ -17,37 +17,45 @@ public class Morph : IDisposable
|
|||
public Morph(float[] vertices, int vertexSize, UMorphTarget morphTarget)
|
||||
{
|
||||
Name = morphTarget.Name;
|
||||
Vertices = new float[vertices.Length / vertexSize * _vertexSize];
|
||||
Vertices = new float[vertices.Length / vertexSize * VertexSize];
|
||||
|
||||
bool TryFindVertex(uint index, out FVector positionDelta)
|
||||
bool TryFindVertex(uint index, out FVector positionDelta, out FVector tangentDelta)
|
||||
{
|
||||
foreach (var vertex in morphTarget.MorphLODModels[0].Vertices)
|
||||
{
|
||||
if (vertex.SourceIdx == index)
|
||||
{
|
||||
positionDelta = vertex.PositionDelta;
|
||||
tangentDelta = vertex.TangentZDelta;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
positionDelta = FVector.ZeroVector;
|
||||
tangentDelta = FVector.ZeroVector;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < vertices.Length; i += vertexSize)
|
||||
{
|
||||
var count = 0;
|
||||
var baseIndex = i / vertexSize * _vertexSize;
|
||||
if (TryFindVertex((uint) vertices[i + 0], out var positionDelta))
|
||||
var baseIndex = i / vertexSize * VertexSize;
|
||||
if (TryFindVertex((uint) vertices[i + 0], out var positionDelta, out var tangentDelta))
|
||||
{
|
||||
Vertices[baseIndex + count++] = vertices[i + 1] + positionDelta.X * Constants.SCALE_DOWN_RATIO;
|
||||
Vertices[baseIndex + count++] = vertices[i + 2] + positionDelta.Z * Constants.SCALE_DOWN_RATIO;
|
||||
Vertices[baseIndex + count++] = vertices[i + 3] + positionDelta.Y * Constants.SCALE_DOWN_RATIO;
|
||||
Vertices[baseIndex + count++] = vertices[i + 7] + tangentDelta.X * Constants.SCALE_DOWN_RATIO;
|
||||
Vertices[baseIndex + count++] = vertices[i + 8] + tangentDelta.Z * Constants.SCALE_DOWN_RATIO;
|
||||
Vertices[baseIndex + count++] = vertices[i + 9] + tangentDelta.Y * Constants.SCALE_DOWN_RATIO;
|
||||
}
|
||||
else
|
||||
{
|
||||
Vertices[baseIndex + count++] = vertices[i + 1];
|
||||
Vertices[baseIndex + count++] = vertices[i + 2];
|
||||
Vertices[baseIndex + count++] = vertices[i + 3];
|
||||
Vertices[baseIndex + count++] = vertices[i + 7];
|
||||
Vertices[baseIndex + count++] = vertices[i + 8];
|
||||
Vertices[baseIndex + count++] = vertices[i + 9];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,16 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using FModel.Views.Snooper.Shading;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
|
||||
namespace FModel.Views.Snooper.Models;
|
||||
|
||||
public class Section : IDisposable
|
||||
public class Section
|
||||
{
|
||||
private int _handle;
|
||||
|
||||
public readonly int MaterialIndex;
|
||||
public readonly int FacesCount;
|
||||
public readonly int FirstFaceIndex;
|
||||
public readonly IntPtr FirstFaceIndexPtr;
|
||||
public readonly Vector3 Color;
|
||||
|
||||
public bool Show;
|
||||
|
||||
|
|
@ -21,22 +20,13 @@ public class Section : IDisposable
|
|||
FacesCount = facesCount;
|
||||
FirstFaceIndex = firstFaceIndex;
|
||||
FirstFaceIndexPtr = new IntPtr(FirstFaceIndex * sizeof(uint));
|
||||
Color = Constants.COLOR_PALETTE[index % Constants.PALETTE_LENGTH];
|
||||
Show = true;
|
||||
}
|
||||
|
||||
public Section(int index, int facesCount, int firstFaceIndex, Material material) : this(index, facesCount, firstFaceIndex)
|
||||
public void SetupMaterial(Material material)
|
||||
{
|
||||
material.IsUsed = true;
|
||||
Show = !material.Parameters.IsNull && !material.Parameters.IsTransparent;
|
||||
}
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
_handle = GL.CreateProgram();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GL.DeleteProgram(_handle);
|
||||
Show = !material.Parameters.IsNull && !material.Parameters.IsTranslucent;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ public class Skybox : IDisposable
|
|||
|
||||
public void Render(Matrix4x4 viewMatrix, Matrix4x4 projMatrix)
|
||||
{
|
||||
GL.Disable(EnableCap.CullFace);
|
||||
GL.DepthFunc(DepthFunction.Lequal);
|
||||
|
||||
_vao.Bind();
|
||||
|
|
@ -101,6 +102,7 @@ public class Skybox : IDisposable
|
|||
GL.DrawArrays(PrimitiveType.Triangles, 0, 36);
|
||||
|
||||
GL.DepthFunc(DepthFunction.Less);
|
||||
GL.Enable(EnableCap.CullFace);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
|
|||
|
|
@ -1,37 +1,60 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
|
||||
namespace FModel.Views.Snooper.Models;
|
||||
|
||||
public struct SocketAttachementInfo
|
||||
{
|
||||
public FGuid Guid;
|
||||
public int Instance;
|
||||
}
|
||||
|
||||
public class Socket : IDisposable
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly string Bone;
|
||||
public readonly FName BoneName;
|
||||
public readonly Transform Transform;
|
||||
public readonly bool IsVirtual;
|
||||
|
||||
public Socket(USkeletalMeshSocket socket, Transform transform)
|
||||
public readonly List<SocketAttachementInfo> AttachedModels;
|
||||
|
||||
private Socket()
|
||||
{
|
||||
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();
|
||||
Transform = Transform.Identity;
|
||||
AttachedModels = new List<SocketAttachementInfo>();
|
||||
}
|
||||
|
||||
public Socket(USkeletalMeshSocket socket, Vector3 position)
|
||||
public Socket(string name, FName boneName, Transform transform, bool isVirtual) : this()
|
||||
{
|
||||
Name = name;
|
||||
BoneName = boneName;
|
||||
Transform = transform;
|
||||
IsVirtual = isVirtual;
|
||||
}
|
||||
|
||||
public Socket(UStaticMeshSocket socket) : this()
|
||||
{
|
||||
Name = socket.SocketName.Text;
|
||||
Bone = socket.BoneName.Text;
|
||||
Transform = Transform.Identity;
|
||||
var pos = position /*+ socket.RelativeRotation.RotateVector(socket.RelativeLocation)*/;
|
||||
Transform.Position = new FVector(pos.X, pos.Z, pos.Y) * Constants.SCALE_DOWN_RATIO;
|
||||
Transform.Rotation = socket.RelativeRotation.Quaternion();
|
||||
Transform.Position = socket.RelativeLocation * Constants.SCALE_DOWN_RATIO;
|
||||
Transform.Scale = socket.RelativeScale;
|
||||
}
|
||||
|
||||
public Socket(USkeletalMeshSocket socket) : this()
|
||||
{
|
||||
Name = socket.SocketName.Text;
|
||||
BoneName = socket.BoneName;
|
||||
Transform.Rotation = socket.RelativeRotation.Quaternion();
|
||||
Transform.Position = socket.RelativeLocation * Constants.SCALE_DOWN_RATIO;
|
||||
Transform.Scale = socket.RelativeScale;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
AttachedModels.Clear();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using CUE4Parse_Conversion;
|
||||
using CUE4Parse_Conversion.Textures;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using FModel.Settings;
|
||||
using FModel.Views.Snooper.Animations;
|
||||
using FModel.Views.Snooper.Lights;
|
||||
using FModel.Views.Snooper.Models;
|
||||
using FModel.Views.Snooper.Shading;
|
||||
|
|
@ -15,14 +20,19 @@ public class Options
|
|||
public FGuid SelectedModel { get; private set; }
|
||||
public int SelectedSection { get; private set; }
|
||||
public int SelectedMorph { get; private set; }
|
||||
public int SelectedAnimation{ get; private set; }
|
||||
|
||||
public readonly Dictionary<FGuid, Model> Models;
|
||||
public readonly Dictionary<FGuid, Texture> Textures;
|
||||
public readonly List<Light> Lights;
|
||||
|
||||
public readonly TimeTracker Tracker;
|
||||
public readonly List<Animation> Animations;
|
||||
|
||||
public readonly Dictionary<string, Texture> Icons;
|
||||
|
||||
private ETexturePlatform _platform;
|
||||
private readonly ETexturePlatform _platform;
|
||||
private readonly string _game;
|
||||
|
||||
public Options()
|
||||
{
|
||||
|
|
@ -30,15 +40,28 @@ public class Options
|
|||
Textures = new Dictionary<FGuid, Texture>();
|
||||
Lights = new List<Light>();
|
||||
|
||||
Tracker = new TimeTracker();
|
||||
Animations = new List<Animation>();
|
||||
|
||||
Icons = new Dictionary<string, Texture>
|
||||
{
|
||||
["material"] = new ("materialicon"),
|
||||
["noimage"] = new ("T_Placeholder_Item_Image"),
|
||||
["pointlight"] = new ("pointlight"),
|
||||
["spotlight"] = new ("spotlight"),
|
||||
["link_on"] = new ("link_on"),
|
||||
["link_off"] = new ("link_off"),
|
||||
["link_has"] = new ("link_has"),
|
||||
["tl_play"] = new ("tl_play"),
|
||||
["tl_pause"] = new ("tl_pause"),
|
||||
["tl_rewind"] = new ("tl_rewind"),
|
||||
["tl_forward"] = new ("tl_forward"),
|
||||
["tl_previous"] = new ("tl_previous"),
|
||||
["tl_next"] = new ("tl_next"),
|
||||
};
|
||||
|
||||
_platform = UserSettings.Default.OverridedPlatform;
|
||||
_game = Services.ApplicationService.ApplicationView.CUE4Parse.Provider.GameName.ToUpper();
|
||||
|
||||
SelectModel(Guid.Empty);
|
||||
}
|
||||
|
|
@ -77,6 +100,72 @@ public class Options
|
|||
SelectedMorph = 0;
|
||||
}
|
||||
|
||||
public void SelectAnimation(int animation)
|
||||
{
|
||||
SelectedAnimation = animation;
|
||||
}
|
||||
|
||||
public void RemoveModel(FGuid guid)
|
||||
{
|
||||
if (!TryGetModel(guid, out var model)) return;
|
||||
|
||||
DetachAndRemoveModels(model, true);
|
||||
model.Dispose();
|
||||
Models.Remove(guid);
|
||||
}
|
||||
|
||||
private void DetachAndRemoveModels(Model model, bool detach)
|
||||
{
|
||||
foreach (var socket in model.Sockets.ToList())
|
||||
{
|
||||
foreach (var info in socket.AttachedModels)
|
||||
{
|
||||
if (!TryGetModel(info.Guid, out var attachedModel)) continue;
|
||||
|
||||
if (attachedModel.IsAnimatedProp)
|
||||
{
|
||||
attachedModel.SafeDetachModel(model);
|
||||
RemoveModel(info.Guid);
|
||||
}
|
||||
else if (detach) attachedModel.SafeDetachModel(model);
|
||||
}
|
||||
|
||||
if (socket.IsVirtual)
|
||||
{
|
||||
socket.Dispose();
|
||||
model.Sockets.Remove(socket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddAnimation(Animation animation)
|
||||
{
|
||||
Animations.Add(animation);
|
||||
}
|
||||
|
||||
public void RemoveAnimations()
|
||||
{
|
||||
Tracker.Reset();
|
||||
SelectedAnimation = 0;
|
||||
|
||||
foreach (var animation in Animations)
|
||||
{
|
||||
foreach (var guid in animation.AttachedModels)
|
||||
{
|
||||
if (!TryGetModel(guid, out var animatedModel)) continue;
|
||||
|
||||
animatedModel.Skeleton.ResetAnimatedData(true);
|
||||
DetachAndRemoveModels(animatedModel, false);
|
||||
}
|
||||
animation.Dispose();
|
||||
}
|
||||
foreach (var kvp in Models.ToList().Where(kvp => kvp.Value.IsAnimatedProp))
|
||||
{
|
||||
RemoveModel(kvp.Key);
|
||||
}
|
||||
Animations.Clear();
|
||||
}
|
||||
|
||||
public void SelectSection(int index)
|
||||
{
|
||||
SelectedSection = index;
|
||||
|
|
@ -91,12 +180,12 @@ public class Options
|
|||
public bool TryGetTexture(UTexture2D o, bool fix, out Texture texture)
|
||||
{
|
||||
var guid = o.LightingGuid;
|
||||
if (!Textures.TryGetValue(guid, out texture) && o.GetFirstMip() is { } mip)
|
||||
if (!Textures.TryGetValue(guid, out texture) && o.GetMipByMaxSize(UserSettings.Default.PreviewMaxTextureSize) is { } mip)
|
||||
{
|
||||
TextureDecoder.DecodeTexture(mip, o.Format, o.isNormalMap, _platform, out var data, out _);
|
||||
if (fix) TextureHelper.FixChannels(o, mip, ref data);
|
||||
|
||||
texture = new Texture(data, mip.SizeX, mip.SizeY, o);
|
||||
if (fix) TextureHelper.FixChannels(_game, texture);
|
||||
Textures[guid] = texture;
|
||||
}
|
||||
return texture != null;
|
||||
|
|
@ -133,7 +222,23 @@ public class Options
|
|||
Services.ApplicationService.ApplicationView.CUE4Parse.ModelIsWaitingAnimation = value;
|
||||
}
|
||||
|
||||
public void ResetModelsAndLights()
|
||||
public bool TrySave(UObject export, out string label, out string savedFilePath)
|
||||
{
|
||||
var exportOptions = new ExporterOptions
|
||||
{
|
||||
LodFormat = UserSettings.Default.LodExportFormat,
|
||||
MeshFormat = UserSettings.Default.MeshExportFormat,
|
||||
MaterialFormat = UserSettings.Default.MaterialExportFormat,
|
||||
TextureFormat = UserSettings.Default.TextureExportFormat,
|
||||
SocketFormat = UserSettings.Default.SocketExportFormat,
|
||||
Platform = 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 ResetModelsLightsAnimations()
|
||||
{
|
||||
foreach (var model in Models.Values)
|
||||
{
|
||||
|
|
@ -141,11 +246,17 @@ public class Options
|
|||
}
|
||||
Models.Clear();
|
||||
Lights.Clear();
|
||||
Tracker.Reset();
|
||||
foreach (var animation in Animations)
|
||||
{
|
||||
animation.Dispose();
|
||||
}
|
||||
Animations.Clear();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ResetModelsAndLights();
|
||||
ResetModelsLightsAnimations();
|
||||
foreach (var texture in Textures.Values)
|
||||
{
|
||||
texture.Dispose();
|
||||
|
|
|
|||
|
|
@ -7,22 +7,35 @@ using CUE4Parse_Conversion.Animations;
|
|||
using CUE4Parse_Conversion.Meshes;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using CUE4Parse.UE4.Assets.Exports.Animation;
|
||||
using CUE4Parse.UE4.Assets.Exports.Component.StaticMesh;
|
||||
using CUE4Parse.UE4.Assets.Exports.Material;
|
||||
using CUE4Parse.UE4.Assets.Exports.SkeletalMesh;
|
||||
using CUE4Parse.UE4.Assets.Exports.StaticMesh;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using CUE4Parse.UE4.Objects.Engine;
|
||||
using CUE4Parse.UE4.Objects.UObject;
|
||||
using FModel.Creator;
|
||||
using FModel.Extensions;
|
||||
using FModel.Settings;
|
||||
using FModel.Views.Snooper.Animations;
|
||||
using FModel.Views.Snooper.Buffers;
|
||||
using FModel.Views.Snooper.Lights;
|
||||
using FModel.Views.Snooper.Models;
|
||||
using FModel.Views.Snooper.Models.Animations;
|
||||
using FModel.Views.Snooper.Shading;
|
||||
|
||||
namespace FModel.Views.Snooper;
|
||||
|
||||
public enum VertexColor
|
||||
{
|
||||
Default,
|
||||
Sections,
|
||||
Colors,
|
||||
Normals,
|
||||
TextureCoordinates
|
||||
}
|
||||
|
||||
public class Renderer : IDisposable
|
||||
{
|
||||
private readonly Skybox _skybox;
|
||||
|
|
@ -30,12 +43,15 @@ public class Renderer : IDisposable
|
|||
private Shader _shader;
|
||||
private Shader _outline;
|
||||
private Shader _light;
|
||||
private bool _saveCameraMode;
|
||||
|
||||
public bool ShowSkybox;
|
||||
public bool ShowGrid;
|
||||
public bool ShowLights;
|
||||
public int VertexColor;
|
||||
public bool AnimateWithRotationOnly;
|
||||
public VertexColor Color;
|
||||
|
||||
public Camera CameraOp { get; }
|
||||
public PickingTexture Picking { get; }
|
||||
public Options Options { get; }
|
||||
|
||||
|
|
@ -44,25 +60,36 @@ public class Renderer : IDisposable
|
|||
_skybox = new Skybox();
|
||||
_grid = new Grid();
|
||||
|
||||
CameraOp = new Camera();
|
||||
Picking = new PickingTexture(width, height);
|
||||
Options = new Options();
|
||||
|
||||
ShowSkybox = UserSettings.Default.ShowSkybox;
|
||||
ShowGrid = UserSettings.Default.ShowGrid;
|
||||
VertexColor = 0; // default
|
||||
AnimateWithRotationOnly = UserSettings.Default.AnimateWithRotationOnly;
|
||||
Color = VertexColor.Default;
|
||||
}
|
||||
|
||||
public Camera Load(CancellationToken cancellationToken, UObject export)
|
||||
public void Load(CancellationToken cancellationToken, UObject export)
|
||||
{
|
||||
ShowLights = false;
|
||||
return export switch
|
||||
_saveCameraMode = export is not UWorld;
|
||||
CameraOp.Mode = _saveCameraMode ? UserSettings.Default.CameraMode : Camera.WorldMode.FlyCam;
|
||||
switch (export)
|
||||
{
|
||||
UStaticMesh st => LoadStaticMesh(st),
|
||||
USkeletalMesh sk => LoadSkeletalMesh(sk),
|
||||
UMaterialInstance mi => LoadMaterialInstance(mi),
|
||||
UWorld wd => LoadWorld(cancellationToken, wd, Transform.Identity),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(export))
|
||||
};
|
||||
case UStaticMesh st:
|
||||
LoadStaticMesh(st);
|
||||
break;
|
||||
case USkeletalMesh sk:
|
||||
LoadSkeletalMesh(sk);
|
||||
break;
|
||||
case UMaterialInstance mi:
|
||||
LoadMaterialInstance(mi);
|
||||
break;
|
||||
case UWorld wd:
|
||||
LoadWorld(cancellationToken, wd, Transform.Identity);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void Swap(UMaterialInstance unrealMaterial)
|
||||
|
|
@ -71,17 +98,108 @@ public class Renderer : IDisposable
|
|||
|
||||
model.Materials[section.MaterialIndex].SwapMaterial(unrealMaterial);
|
||||
Application.Current.Dispatcher.Invoke(() => model.Materials[section.MaterialIndex].Setup(Options, model.UvCount));
|
||||
Options.SwapMaterial(false);
|
||||
}
|
||||
|
||||
public void Animate(UAnimSequence animSequence)
|
||||
public void Animate(UObject anim) => Animate(anim, Options.SelectedModel);
|
||||
private void Animate(UObject anim, FGuid guid)
|
||||
{
|
||||
if (!Options.TryGetModel(out var model) || !model.Skeleton.IsLoaded ||
|
||||
model.Skeleton?.RefSkel.ConvertAnims(animSequence) is not { } anim || anim.Sequences.Count == 0)
|
||||
if (!Options.TryGetModel(guid, out var model) || !model.HasSkeleton)
|
||||
return;
|
||||
|
||||
model.Skeleton.Anim = new Animation(anim);
|
||||
Options.AnimateMesh(false);
|
||||
float maxElapsedTime;
|
||||
switch (anim)
|
||||
{
|
||||
case UAnimSequence animSequence when animSequence.Skeleton.TryLoad(out USkeleton skeleton):
|
||||
{
|
||||
var animSet = skeleton.ConvertAnims(animSequence);
|
||||
var animation = new Animation(animSequence, animSet, guid);
|
||||
maxElapsedTime = animation.TotalElapsedTime;
|
||||
model.Skeleton.Animate(animSet, AnimateWithRotationOnly);
|
||||
Options.AddAnimation(animation);
|
||||
break;
|
||||
}
|
||||
case UAnimMontage animMontage when animMontage.Skeleton.TryLoad(out USkeleton skeleton):
|
||||
{
|
||||
var animSet = skeleton.ConvertAnims(animMontage);
|
||||
var animation = new Animation(animMontage, animSet, guid);
|
||||
maxElapsedTime = animation.TotalElapsedTime;
|
||||
model.Skeleton.Animate(animSet, AnimateWithRotationOnly);
|
||||
Options.AddAnimation(animation);
|
||||
|
||||
foreach (var notifyEvent in animMontage.Notifies)
|
||||
{
|
||||
if (!notifyEvent.NotifyStateClass.TryLoad(out UObject notifyClass) ||
|
||||
!notifyClass.TryGetValue(out FPackageIndex meshProp, "SkeletalMeshProp", "StaticMeshProp", "Mesh") ||
|
||||
!meshProp.TryLoad(out UObject export)) continue;
|
||||
|
||||
var t = Transform.Identity;
|
||||
if (notifyClass.TryGetValue(out FTransform offset, "Offset"))
|
||||
{
|
||||
t.Rotation = offset.Rotation;
|
||||
t.Position = offset.Translation * Constants.SCALE_DOWN_RATIO;
|
||||
t.Scale = offset.Scale3D;
|
||||
}
|
||||
|
||||
switch (export)
|
||||
{
|
||||
case UStaticMesh st:
|
||||
{
|
||||
guid = st.LightingGuid;
|
||||
if (Options.TryGetModel(guid, out var instancedModel))
|
||||
instancedModel.AddInstance(t);
|
||||
else if (st.TryConvert(out var mesh))
|
||||
Options.Models[guid] = new Model(st, mesh, t);
|
||||
break;
|
||||
}
|
||||
case USkeletalMesh sk:
|
||||
{
|
||||
guid = Guid.NewGuid();
|
||||
if (!Options.Models.ContainsKey(guid) && sk.TryConvert(out var mesh))
|
||||
Options.Models[guid] = new Model(sk, mesh, t);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
if (!Options.TryGetModel(guid, out var addedModel))
|
||||
continue;
|
||||
|
||||
addedModel.IsAnimatedProp = true;
|
||||
if (notifyClass.TryGetValue(out UObject skeletalMeshPropAnimation, "SkeletalMeshPropAnimation", "Animation"))
|
||||
Animate(skeletalMeshPropAnimation, guid);
|
||||
if (notifyClass.TryGetValue(out FName socketName, "SocketName"))
|
||||
{
|
||||
t = Transform.Identity;
|
||||
if (notifyClass.TryGetValue(out FVector location, "LocationOffset", "Location"))
|
||||
t.Position = location * Constants.SCALE_DOWN_RATIO;
|
||||
if (notifyClass.TryGetValue(out FRotator rotation, "RotationOffset", "Rotation"))
|
||||
t.Rotation = rotation.Quaternion();
|
||||
if (notifyClass.TryGetValue(out FVector scale, "Scale"))
|
||||
t.Scale = scale;
|
||||
|
||||
var s = new Socket($"TL_{addedModel.Name}", socketName, t, true);
|
||||
model.Sockets.Add(s);
|
||||
addedModel.AttachModel(model, s, new SocketAttachementInfo { Guid = guid, Instance = addedModel.SelectedInstance });
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case UAnimComposite animComposite when animComposite.Skeleton.TryLoad(out USkeleton skeleton):
|
||||
{
|
||||
var animSet = skeleton.ConvertAnims(animComposite);
|
||||
var animation = new Animation(animComposite, animSet, guid);
|
||||
maxElapsedTime = animation.TotalElapsedTime;
|
||||
model.Skeleton.Animate(animSet, AnimateWithRotationOnly);
|
||||
Options.AddAnimation(animation);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
Options.Tracker.IsPaused = false;
|
||||
Options.Tracker.SafeSetMaxElapsedTime(maxElapsedTime);
|
||||
}
|
||||
|
||||
public void Setup()
|
||||
|
|
@ -97,21 +215,33 @@ public class Renderer : IDisposable
|
|||
Options.SetupModelsAndLights();
|
||||
}
|
||||
|
||||
public void Render(Camera cam)
|
||||
public void Render(float deltaSeconds)
|
||||
{
|
||||
var viewMatrix = cam.GetViewMatrix();
|
||||
var projMatrix = cam.GetProjectionMatrix();
|
||||
var viewMatrix = CameraOp.GetViewMatrix();
|
||||
var projMatrix = CameraOp.GetProjectionMatrix();
|
||||
|
||||
if (ShowSkybox) _skybox.Render(viewMatrix, projMatrix);
|
||||
if (ShowGrid) _grid.Render(viewMatrix, projMatrix, cam.Near, cam.Far);
|
||||
if (ShowGrid) _grid.Render(viewMatrix, projMatrix, CameraOp.Near, CameraOp.Far);
|
||||
|
||||
_shader.Render(viewMatrix, cam.Position, projMatrix);
|
||||
for (int i = 0; i < 6; i++)
|
||||
_shader.SetUniform($"bVertexColors[{i}]", i == VertexColor);
|
||||
_shader.Render(viewMatrix, CameraOp.Position, projMatrix);
|
||||
for (int i = 0; i < 5; i++)
|
||||
_shader.SetUniform($"bVertexColors[{i}]", i == (int) Color);
|
||||
|
||||
// update animations
|
||||
if (Options.Animations.Count > 0) Options.Tracker.Update(deltaSeconds);
|
||||
foreach (var animation in Options.Animations)
|
||||
{
|
||||
animation.TimeCalculation(Options.Tracker.ElapsedTime);
|
||||
foreach (var guid in animation.AttachedModels.Where(guid => Options.Models[guid].HasSkeleton))
|
||||
{
|
||||
Options.Models[guid].Skeleton.UpdateAnimationMatrices(animation.CurrentSequence, animation.FrameInSequence);
|
||||
}
|
||||
}
|
||||
|
||||
// render model pass
|
||||
foreach (var model in Options.Models.Values)
|
||||
{
|
||||
model.UpdateMatrices(Options);
|
||||
if (!model.Show) continue;
|
||||
model.Render(_shader);
|
||||
}
|
||||
|
|
@ -132,67 +262,79 @@ public class Renderer : IDisposable
|
|||
// outline pass
|
||||
if (Options.TryGetModel(out var selected) && selected.Show)
|
||||
{
|
||||
_outline.Render(viewMatrix, cam.Position, projMatrix);
|
||||
selected.Outline(_outline);
|
||||
_outline.Render(viewMatrix, CameraOp.Position, projMatrix);
|
||||
selected.Render(_outline, true);
|
||||
}
|
||||
|
||||
// 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)
|
||||
private void 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;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!original.TryConvert(out var mesh))
|
||||
return null;
|
||||
return;
|
||||
|
||||
Options.Models[guid] = new Model(original, mesh);
|
||||
Options.SelectModel(guid);
|
||||
return SetupCamera(mesh.BoundingBox *= Constants.SCALE_DOWN_RATIO);
|
||||
SetupCamera(Options.Models[guid].Box);
|
||||
}
|
||||
|
||||
private Camera LoadSkeletalMesh(USkeletalMesh original)
|
||||
private void LoadSkeletalMesh(USkeletalMesh original)
|
||||
{
|
||||
var guid = Guid.NewGuid();
|
||||
if (Options.Models.ContainsKey(guid) || !original.TryConvert(out var mesh)) return null;
|
||||
var guid = new FGuid((uint) original.GetFullName().GetHashCode());
|
||||
if (Options.Models.ContainsKey(guid) || !original.TryConvert(out var mesh)) return;
|
||||
|
||||
Options.Models[guid] = new Model(original, mesh);
|
||||
Options.SelectModel(guid);
|
||||
return SetupCamera(mesh.BoundingBox *= Constants.SCALE_DOWN_RATIO);
|
||||
SetupCamera(Options.Models[guid].Box);
|
||||
}
|
||||
|
||||
private Camera LoadMaterialInstance(UMaterialInstance original)
|
||||
private void LoadMaterialInstance(UMaterialInstance original)
|
||||
{
|
||||
var guid = Guid.NewGuid();
|
||||
if (Options.Models.ContainsKey(guid)) return null;
|
||||
if (!Utils.TryLoadObject("Engine/Content/EditorMeshes/EditorCube.EditorCube", out UStaticMesh editorCube))
|
||||
return;
|
||||
|
||||
Options.Models[guid] = new Cube(original);
|
||||
var guid = editorCube.LightingGuid;
|
||||
if (Options.TryGetModel(guid, out var model))
|
||||
{
|
||||
model.Materials[0].SwapMaterial(original);
|
||||
Application.Current.Dispatcher.Invoke(() => model.Materials[0].Setup(Options, model.UvCount));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!editorCube.TryConvert(out var mesh))
|
||||
return;
|
||||
|
||||
Options.Models[guid] = new Cube(mesh, original);
|
||||
Options.SelectModel(guid);
|
||||
return SetupCamera(new FBox(new FVector(-.65f), new FVector(.65f)));
|
||||
SetupCamera(Options.Models[guid].Box);
|
||||
}
|
||||
|
||||
private Camera LoadWorld(CancellationToken cancellationToken, UWorld original, Transform transform)
|
||||
private void SetupCamera(FBox box) => CameraOp.Setup(box);
|
||||
|
||||
private void LoadWorld(CancellationToken cancellationToken, UWorld original, Transform transform)
|
||||
{
|
||||
var cam = new Camera(new Vector3(0f, 5f, 5f), Vector3.Zero, 0.01f, 1000f, 5f);
|
||||
CameraOp.Setup(new FBox(FVector.ZeroVector, new FVector(0, 10, 10)));
|
||||
if (original.PersistentLevel.Load<ULevel>() is not { } persistentLevel)
|
||||
return cam;
|
||||
return;
|
||||
|
||||
if (persistentLevel.TryGetValue(out FSoftObjectPath runtimeCell, "WorldPartitionRuntimeCell") &&
|
||||
Utils.TryLoadObject(runtimeCell.AssetPathName.Text.SubstringBeforeWithLast(".") + runtimeCell.SubPathString.SubstringAfterLast("."), out UObject worldPartition))
|
||||
{
|
||||
var position = worldPartition.GetOrDefault("Position", FVector.ZeroVector) * Constants.SCALE_DOWN_RATIO;
|
||||
var box = worldPartition.GetOrDefault("ContentBounds", new FBox(FVector.ZeroVector, FVector.OneVector));
|
||||
box *= MathF.Pow(Constants.SCALE_DOWN_RATIO, 2);
|
||||
CameraOp.Teleport(new Vector3(position.X, position.Z, position.Y), box, true);
|
||||
}
|
||||
|
||||
var length = persistentLevel.Actors.Length;
|
||||
for (var i = 0; i < length; i++)
|
||||
|
|
@ -204,57 +346,58 @@ public class Renderer : IDisposable
|
|||
continue;
|
||||
|
||||
Services.ApplicationService.ApplicationView.Status.UpdateStatusLabel($"{original.Name} ... {i}/{length}");
|
||||
WorldCamera(actor, ref cam);
|
||||
// WorldLight(actor);
|
||||
WorldCamera(actor);
|
||||
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)
|
||||
private void WorldCamera(UObject actor)
|
||||
{
|
||||
if (actor.ExportType != "LevelBounds" || !actor.TryGetValue(out FPackageIndex boxComponent, "BoxComponent") ||
|
||||
boxComponent.Load() is not { } boxObject) return;
|
||||
|
||||
var direction = boxObject.GetOrDefault("RelativeLocation", FVector.ZeroVector).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));
|
||||
var direction = boxObject.GetOrDefault("RelativeLocation", FVector.ZeroVector) * Constants.SCALE_DOWN_RATIO;
|
||||
var position = boxObject.GetOrDefault("RelativeScale3D", FVector.OneVector) / 2f * Constants.SCALE_DOWN_RATIO;
|
||||
CameraOp.Setup(new FBox(direction, position));
|
||||
}
|
||||
|
||||
private void WorldLight(UObject actor)
|
||||
{
|
||||
// if (!actor.TryGetValue(out FPackageIndex lightComponent, "LightComponent") ||
|
||||
// lightComponent.Load() is not { } lightObject) return;
|
||||
//
|
||||
// Cache.Lights.Add(new PointLight(Cache.Icons["pointlight"], lightObject, FVector.ZeroVector));
|
||||
if (!actor.TryGetValue(out FPackageIndex lightComponent, "LightComponent") ||
|
||||
lightComponent.Load() is not { } lightObject) return;
|
||||
|
||||
switch (actor.ExportType)
|
||||
{
|
||||
case "PointLight":
|
||||
Options.Lights.Add(new PointLight(Options.Icons["pointlight"], lightObject));
|
||||
break;
|
||||
case "SpotLight":
|
||||
Options.Lights.Add(new SpotLight(Options.Icons["spotlight"], lightObject));
|
||||
break;
|
||||
case "RectLight":
|
||||
case "SkyLight":
|
||||
case "DirectionalLight":
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void WorldMesh(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)
|
||||
!staticMeshComponent.TryLoad(out UStaticMeshComponent staticMeshComp) ||
|
||||
!staticMeshComp.GetStaticMesh().TryLoad(out 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()
|
||||
Position = staticMeshComp.GetOrDefault("RelativeLocation", FVector.ZeroVector) * Constants.SCALE_DOWN_RATIO,
|
||||
Rotation = staticMeshComp.GetOrDefault("RelativeRotation", FRotator.ZeroRotator).Quaternion(),
|
||||
Scale = staticMeshComp.GetOrDefault("RelativeScale3D", FVector.OneVector)
|
||||
};
|
||||
|
||||
if (Options.TryGetModel(guid, out var model))
|
||||
|
|
@ -264,6 +407,8 @@ public class Renderer : IDisposable
|
|||
else if (m.TryConvert(out var mesh))
|
||||
{
|
||||
model = new Model(m, mesh, t);
|
||||
model.TwoSided = actor.GetOrDefault("bMirrored", staticMeshComp.GetOrDefault("bDisallowMeshPaintPerInstance", model.TwoSided));
|
||||
|
||||
if (actor.TryGetValue(out FPackageIndex baseMaterial, "BaseMaterial") &&
|
||||
actor.TryGetAllValues(out FPackageIndex[] textureData, "TextureData"))
|
||||
{
|
||||
|
|
@ -300,6 +445,7 @@ public class Renderer : IDisposable
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (staticMeshComp.TryGetValue(out FPackageIndex[] overrideMaterials, "OverrideMaterials"))
|
||||
{
|
||||
var max = model.Sections.Length - 1;
|
||||
|
|
@ -311,18 +457,19 @@ public class Renderer : IDisposable
|
|||
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));
|
||||
Options.Lights.Add(new PointLight(guid, Options.Icons["pointlight"], pl1, pl2, t));
|
||||
}
|
||||
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));
|
||||
Options.Lights.Add(new SpotLight(guid, Options.Icons["spotlight"], sl1, sl2, t));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -342,20 +489,32 @@ public class Renderer : IDisposable
|
|||
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()
|
||||
Position = staticMeshComp.GetOrDefault("RelativeLocation", FVector.ZeroVector) * Constants.SCALE_DOWN_RATIO,
|
||||
Rotation = staticMeshComp.GetOrDefault("RelativeRotation", FRotator.ZeroRotator).Quaternion()
|
||||
};
|
||||
|
||||
for (int j = 0; j < additionalWorlds.Length; j++)
|
||||
if (Creator.Utils.TryLoadObject(additionalWorlds[j].AssetPathName.Text, out UWorld w))
|
||||
if (Utils.TryLoadObject(additionalWorlds[j].AssetPathName.Text, out UWorld w))
|
||||
LoadWorld(cancellationToken, w, transform);
|
||||
}
|
||||
|
||||
public void WindowResized(int width, int height)
|
||||
{
|
||||
CameraOp.AspectRatio = width / (float) height;
|
||||
Picking.WindowResized(width, height);
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
Options.ResetModelsLightsAnimations();
|
||||
Options.SelectModel(Guid.Empty);
|
||||
Options.SwapMaterial(false);
|
||||
Options.AnimateMesh(false);
|
||||
|
||||
if (_saveCameraMode) UserSettings.Default.CameraMode = CameraOp.Mode;
|
||||
UserSettings.Default.ShowSkybox = ShowSkybox;
|
||||
UserSettings.Default.ShowGrid = ShowGrid;
|
||||
UserSettings.Default.AnimateWithRotationOnly = AnimateWithRotationOnly;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ using CUE4Parse.UE4.Assets.Exports.Material;
|
|||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using FModel.Extensions;
|
||||
using FModel.Settings;
|
||||
using FModel.Views.Snooper.Models;
|
||||
using ImGuiNET;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
|
|
@ -18,6 +20,7 @@ public class Material : IDisposable
|
|||
|
||||
public readonly CMaterialParams2 Parameters;
|
||||
public string Name;
|
||||
public string Path;
|
||||
public int SelectedChannel;
|
||||
public int SelectedTexture;
|
||||
public bool IsUsed;
|
||||
|
|
@ -34,15 +37,15 @@ public class Material : IDisposable
|
|||
public AoParams Ao;
|
||||
public bool HasAo;
|
||||
|
||||
public float Specular = 1f;
|
||||
public float Roughness = 0.5f;
|
||||
public float RoughnessMin = 0f;
|
||||
public float RoughnessMax = 1f;
|
||||
public float EmissiveMult = 1f;
|
||||
public float UVScale = 1f;
|
||||
|
||||
public Material()
|
||||
{
|
||||
Parameters = new CMaterialParams2();
|
||||
Name = "";
|
||||
Path = "None";
|
||||
IsUsed = false;
|
||||
|
||||
Diffuse = Array.Empty<Texture>();
|
||||
|
|
@ -63,7 +66,8 @@ public class Material : IDisposable
|
|||
public void SwapMaterial(UMaterialInterface unrealMaterial)
|
||||
{
|
||||
Name = unrealMaterial.Name;
|
||||
unrealMaterial.GetParams(Parameters, EMaterialFormat.AllLayers);
|
||||
Path = unrealMaterial.GetPathName();
|
||||
unrealMaterial.GetParams(Parameters, UserSettings.Default.MaterialExportFormat);
|
||||
}
|
||||
|
||||
public void Setup(Options options, int uvCount)
|
||||
|
|
@ -73,8 +77,8 @@ public class Material : IDisposable
|
|||
if (uvCount < 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];
|
||||
Normals = new[] { new Texture(new FLinearColor(0.498f, 0.498f, 0.996f, 1f)) };
|
||||
SpecularMasks = new [] { new Texture(new FLinearColor(1f, 0.5f, 0.5f, 1f)) };
|
||||
Emissive = new Texture[1];
|
||||
DiffuseColor = new[] { new Vector4(0.5f) };
|
||||
EmissiveColor = new[] { Vector4.One };
|
||||
|
|
@ -106,24 +110,22 @@ public class Material : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
// scalars
|
||||
if (Parameters.TryGetScalar(out var specular, "Specular", "Specular Intensity", "Spec"))
|
||||
Specular = specular;
|
||||
|
||||
if (Parameters.TryGetScalar(out var roughnessMin, "RoughnessMin", "SpecRoughnessMin") &&
|
||||
Parameters.TryGetScalar(out var roughnessMax, "RoughnessMax", "SpecRoughnessMax"))
|
||||
Roughness = (roughnessMin + roughnessMax) / 2f;
|
||||
if (Parameters.TryGetScalar(out var roughnessMin, "RoughnessMin", "SpecRoughnessMin"))
|
||||
RoughnessMin = roughnessMin;
|
||||
if (Parameters.TryGetScalar(out var roughnessMax, "RoughnessMax", "SpecRoughnessMax"))
|
||||
RoughnessMax = roughnessMax;
|
||||
if (Parameters.TryGetScalar(out var roughness, "Rough", "Roughness", "Ro Multiplier", "RO_mul", "Roughness_Mult"))
|
||||
Roughness = roughness;
|
||||
{
|
||||
var d = roughness / 2;
|
||||
RoughnessMin = roughness - d;
|
||||
RoughnessMax = roughness + d;
|
||||
}
|
||||
|
||||
if (Parameters.TryGetScalar(out var emissiveMultScalar, "emissive mult", "Emissive_Mult", "EmissiveIntensity", "EmissionIntensity"))
|
||||
EmissiveMult = emissiveMultScalar;
|
||||
else if (Parameters.TryGetLinearColor(out var emissiveMultColor, "Emissive Multiplier", "EmissiveMultiplier"))
|
||||
EmissiveMult = emissiveMultColor.R;
|
||||
|
||||
if (Parameters.TryGetScalar(out var uvScale, "UV Scale"))
|
||||
UVScale = uvScale;
|
||||
|
||||
if (Parameters.TryGetLinearColor(out var EmissiveUVs,
|
||||
"EmissiveUVs_RG_UpperLeftCorner_BA_LowerRightCorner",
|
||||
"Emissive Texture UVs RG_TopLeft BA_BottomRight",
|
||||
|
|
@ -228,10 +230,9 @@ public class Material : IDisposable
|
|||
shader.SetUniform("uParameters.HasAo", HasAo);
|
||||
|
||||
shader.SetUniform("uParameters.EmissiveRegion", EmissiveRegion);
|
||||
shader.SetUniform("uParameters.Specular", Specular);
|
||||
shader.SetUniform("uParameters.Roughness", Roughness);
|
||||
shader.SetUniform("uParameters.RoughnessMin", RoughnessMin);
|
||||
shader.SetUniform("uParameters.RoughnessMax", RoughnessMax);
|
||||
shader.SetUniform("uParameters.EmissiveMult", EmissiveMult);
|
||||
shader.SetUniform("uParameters.UVScale", UVScale);
|
||||
}
|
||||
|
||||
private const string _mult = "x %.2f";
|
||||
|
|
@ -244,14 +245,12 @@ public class Material : IDisposable
|
|||
if (ImGui.BeginTable("parameters", 2))
|
||||
{
|
||||
var id = 1;
|
||||
SnimGui.Layout("Specular");ImGui.PushID(id++);
|
||||
ImGui.DragFloat("", ref Specular, _step, _zero, 1.0f, _mult, _clamp);
|
||||
ImGui.PopID();SnimGui.Layout("Roughness");ImGui.PushID(id++);
|
||||
ImGui.DragFloat("", ref Roughness, _step, _zero, 1.0f, _mult, _clamp);
|
||||
SnimGui.Layout("Roughness Min");ImGui.PushID(id++);
|
||||
ImGui.DragFloat("", ref RoughnessMin, _step, _zero, 1.0f, _mult, _clamp);
|
||||
ImGui.PopID();SnimGui.Layout("Roughness Max");ImGui.PushID(id++);
|
||||
ImGui.DragFloat("", ref RoughnessMax, _step, _zero, 1.0f, _mult, _clamp);
|
||||
ImGui.PopID();SnimGui.Layout("Emissive Multiplier");ImGui.PushID(id++);
|
||||
ImGui.DragFloat("", ref EmissiveMult, _step, _zero, _infinite, _mult, _clamp);
|
||||
ImGui.PopID();SnimGui.Layout("UV Scale");ImGui.PushID(id++);
|
||||
ImGui.DragFloat("", ref UVScale, _step, _zero, _infinite, _mult, _clamp);
|
||||
ImGui.PopID();
|
||||
|
||||
if (HasAo)
|
||||
|
|
@ -271,17 +270,23 @@ public class Material : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
public void ImGuiBaseProperties(string id)
|
||||
{
|
||||
if (ImGui.BeginTable(id, 2, ImGuiTableFlags.SizingStretchProp))
|
||||
{
|
||||
Layout("Blend", Parameters.BlendMode.GetDescription(), true, true);
|
||||
Layout("Shading", Parameters.ShadingModel.GetDescription(), true, true);
|
||||
ImGui.EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
public void ImGuiDictionaries<T>(string id, Dictionary<string, T> dictionary, bool center = false, bool wrap = false)
|
||||
{
|
||||
if (ImGui.BeginTable(id, 2))
|
||||
{
|
||||
foreach ((string key, T value) in dictionary.Reverse())
|
||||
{
|
||||
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);
|
||||
Layout(key, value, center, wrap);
|
||||
}
|
||||
ImGui.EndTable();
|
||||
}
|
||||
|
|
@ -332,31 +337,7 @@ public class Material : IDisposable
|
|||
return ImGui.IsItemHovered() && ImGui.IsMouseDoubleClicked(ImGuiMouseButton.Left);
|
||||
}
|
||||
|
||||
public Vector2[] ImGuiTextureInspector(Texture fallback)
|
||||
{
|
||||
var texture = GetSelectedTexture() ?? fallback;
|
||||
if (ImGui.BeginTable("texture_inspector", 2, ImGuiTableFlags.SizingStretchProp))
|
||||
{
|
||||
SnimGui.Layout("Type");ImGui.Text($" : ({texture.Format}) {texture.Name}");
|
||||
SnimGui.TooltipCopy("(?) Click to Copy Path", texture.Path);
|
||||
SnimGui.Layout("Guid");ImGui.Text($" : {texture.Guid.ToString(EGuidFormats.UniqueObjectGuid)}");
|
||||
SnimGui.Layout("Import");ImGui.Text($" : {texture.ImportedWidth}x{texture.ImportedHeight}");
|
||||
SnimGui.Layout("Export");ImGui.Text($" : {texture.Width}x{texture.Height}");
|
||||
ImGui.EndTable();
|
||||
}
|
||||
|
||||
var largest = ImGui.GetContentRegionAvail();
|
||||
largest.X -= ImGui.GetScrollX();
|
||||
largest.Y -= ImGui.GetScrollY();
|
||||
|
||||
var ratio = Math.Min(largest.X / texture.Width, largest.Y / texture.Height);
|
||||
var size = new Vector2(texture.Width * ratio, texture.Height * ratio);
|
||||
var pos = ImGui.GetCursorPos();
|
||||
ImGui.Image(texture.GetPointer(),size, Vector2.Zero, Vector2.One, Vector4.One, new Vector4(1.0f, 1.0f, 1.0f, 0.25f));
|
||||
return new[] { size, pos };
|
||||
}
|
||||
|
||||
private Texture GetSelectedTexture()
|
||||
public Texture GetSelectedTexture()
|
||||
{
|
||||
return SelectedTexture switch
|
||||
{
|
||||
|
|
@ -369,6 +350,15 @@ public class Material : IDisposable
|
|||
};
|
||||
}
|
||||
|
||||
private void Layout<T>(string key, T value, bool center = false, bool wrap = false)
|
||||
{
|
||||
SnimGui.Layout(key, true);
|
||||
var text = $"{value:N}";
|
||||
if (center) ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (ImGui.GetColumnWidth() - ImGui.CalcTextSize(text).X) / 2);
|
||||
if (wrap) ImGui.TextWrapped(text); else ImGui.Text(text);
|
||||
SnimGui.TooltipCopy(text);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
for (int i = 0; i < Diffuse.Length; i++)
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ public class Shader : IDisposable
|
|||
var handle = GL.CreateShader(type);
|
||||
|
||||
var content = reader.ReadToEnd();
|
||||
if (file.Equals("default.frag") && GL.GetInteger(GetPName.MaxTextureUnits) == 0)
|
||||
if (file.Equals("default.frag") && GL.GetInteger(GetPName.MaxTextureCoords) == 0)
|
||||
content = content.Replace("#define MAX_UV_COUNT 8", "#define MAX_UV_COUNT 1");
|
||||
|
||||
GL.ShaderSource(handle, content);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using System.Windows;
|
||||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using CUE4Parse.UE4.Objects.Core.Math;
|
||||
using CUE4Parse.UE4.Objects.Core.Misc;
|
||||
using ImGuiNET;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
|
|
@ -25,6 +27,17 @@ public class Texture : IDisposable
|
|||
public int Width;
|
||||
public int Height;
|
||||
|
||||
private const int DisabledChannel = (int)BlendingFactor.Zero;
|
||||
private readonly bool[] _values = { true, true, true, true };
|
||||
private readonly string[] _labels = { "R", "G", "B", "A" };
|
||||
public int[] SwizzleMask =
|
||||
{
|
||||
(int) PixelFormat.Red,
|
||||
(int) PixelFormat.Green,
|
||||
(int) PixelFormat.Blue,
|
||||
(int) PixelFormat.Alpha
|
||||
};
|
||||
|
||||
public Texture(TextureType type)
|
||||
{
|
||||
_handle = GL.GenTexture();
|
||||
|
|
@ -46,12 +59,6 @@ public class Texture : IDisposable
|
|||
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);
|
||||
}
|
||||
|
||||
|
|
@ -64,7 +71,7 @@ public class Texture : IDisposable
|
|||
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.TextureMagFilter, (int) TextureMagFilter.Linear);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureWrapS, (int) TextureWrapMode.ClampToEdge);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureWrapT, (int) TextureWrapMode.ClampToEdge);
|
||||
|
||||
|
|
@ -86,7 +93,7 @@ public class Texture : IDisposable
|
|||
|
||||
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.TextureMagFilter, (int) TextureMagFilter.Linear);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureBaseLevel, 0);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureMaxLevel, 8);
|
||||
|
||||
|
|
@ -103,7 +110,7 @@ public class Texture : IDisposable
|
|||
|
||||
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.TextureMagFilter, (int) TextureMagFilter.Linear);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureBaseLevel, 0);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureMaxLevel, 8);
|
||||
|
||||
|
|
@ -119,11 +126,13 @@ public class Texture : IDisposable
|
|||
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.TextureMinFilter, (int) TextureMinFilter.LinearMipmapLinear);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureWrapR, (int) TextureWrapMode.ClampToEdge);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureWrapS, (int) TextureWrapMode.ClampToEdge);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureWrapT, (int) TextureWrapMode.ClampToEdge);
|
||||
|
||||
GL.GenerateMipmap(GenerateMipmapTarget.TextureCubeMap);
|
||||
}
|
||||
|
||||
public Texture(string texture) : this(TextureType.Normal)
|
||||
|
|
@ -133,7 +142,7 @@ public class Texture : IDisposable
|
|||
ProcessPixels(texture, _target);
|
||||
|
||||
GL.TexParameter(_target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.Linear);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureMagFilter, (int) TextureMinFilter.Linear);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureWrapR, (int) TextureWrapMode.ClampToEdge);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureWrapS, (int) TextureWrapMode.ClampToEdge);
|
||||
GL.TexParameter(_target, TextureParameterName.TextureWrapT, (int) TextureWrapMode.ClampToEdge);
|
||||
|
|
@ -171,6 +180,12 @@ public class Texture : IDisposable
|
|||
GL.BindTexture(_target, _handle);
|
||||
}
|
||||
|
||||
public void Swizzle()
|
||||
{
|
||||
Bind();
|
||||
GL.TexParameter(_target, TextureParameterName.TextureSwizzleRgba, SwizzleMask);
|
||||
}
|
||||
|
||||
public IntPtr GetPointer() => (IntPtr) _handle;
|
||||
|
||||
public void WindowResized(int width, int height)
|
||||
|
|
@ -198,6 +213,84 @@ public class Texture : IDisposable
|
|||
{
|
||||
GL.DeleteTexture(_handle);
|
||||
}
|
||||
|
||||
private Vector3 _scrolling = new (0.0f, 0.0f, 1.0f);
|
||||
public void ImGuiTextureInspector()
|
||||
{
|
||||
if (ImGui.BeginTable("texture_inspector", 2, ImGuiTableFlags.SizingStretchProp))
|
||||
{
|
||||
SnimGui.NoFramePaddingOnY(() =>
|
||||
{
|
||||
SnimGui.Layout("Type");ImGui.Text($" : ({Format}) {Name}");
|
||||
SnimGui.TooltipCopy("(?) Click to Copy Path", Path);
|
||||
SnimGui.Layout("Guid");ImGui.Text($" : {Guid.ToString(EGuidFormats.UniqueObjectGuid)}");
|
||||
SnimGui.Layout("Import");ImGui.Text($" : {ImportedWidth}x{ImportedHeight}");
|
||||
SnimGui.Layout("Export");ImGui.Text($" : {Width}x{Height}");
|
||||
|
||||
SnimGui.Layout("Swizzle");
|
||||
for (int c = 0; c < SwizzleMask.Length; c++)
|
||||
{
|
||||
if (ImGui.Checkbox(_labels[c], ref _values[c]))
|
||||
{
|
||||
Bind();
|
||||
GL.TexParameter(_target, TextureParameterName.TextureSwizzleR + c, _values[c] ? SwizzleMask[c] : DisabledChannel);
|
||||
}
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
ImGui.EndTable();
|
||||
});
|
||||
}
|
||||
|
||||
var io = ImGui.GetIO();
|
||||
var canvasP0 = ImGui.GetCursorScreenPos();
|
||||
var canvasSize = ImGui.GetContentRegionAvail();
|
||||
if (canvasSize.X < 50.0f) canvasSize.X = 50.0f;
|
||||
if (canvasSize.Y < 50.0f) canvasSize.Y = 50.0f;
|
||||
var canvasP1 = canvasP0 + canvasSize;
|
||||
var origin = new Vector2(canvasP0.X + _scrolling.X, canvasP0.Y + _scrolling.Y);
|
||||
var absoluteMiddle = canvasSize / 2.0f;
|
||||
|
||||
ImGui.InvisibleButton("texture_inspector_canvas", canvasSize, ImGuiButtonFlags.MouseButtonLeft);
|
||||
if (ImGui.IsItemActive() && ImGui.IsMouseDragging(ImGuiMouseButton.Left))
|
||||
{
|
||||
_scrolling.X += io.MouseDelta.X;
|
||||
_scrolling.Y += io.MouseDelta.Y;
|
||||
}
|
||||
else if (ImGui.IsItemHovered() && io.MouseWheel != 0.0f)
|
||||
{
|
||||
var zoomFactor = 1.0f + io.MouseWheel * 0.1f;
|
||||
var mousePosCanvas = io.MousePos - origin;
|
||||
|
||||
_scrolling.X -= (mousePosCanvas.X - absoluteMiddle.X) * (zoomFactor - 1);
|
||||
_scrolling.Y -= (mousePosCanvas.Y - absoluteMiddle.Y) * (zoomFactor - 1);
|
||||
_scrolling.Z *= zoomFactor;
|
||||
origin = new Vector2(canvasP0.X + _scrolling.X, canvasP0.Y + _scrolling.Y);
|
||||
}
|
||||
|
||||
var drawList = ImGui.GetWindowDrawList();
|
||||
drawList.AddRectFilled(canvasP0, canvasP1, 0xFF242424);
|
||||
drawList.PushClipRect(canvasP0, canvasP1, true);
|
||||
{
|
||||
var sensitivity = _scrolling.Z * 25.0f;
|
||||
for (float x = _scrolling.X % sensitivity; x < canvasSize.X; x += sensitivity)
|
||||
drawList.AddLine(canvasP0 with { X = canvasP0.X + x }, canvasP1 with { X = canvasP0.X + x }, 0x28C8C8C8);
|
||||
for (float y = _scrolling.Y % sensitivity; y < canvasSize.Y; y += sensitivity)
|
||||
drawList.AddLine(canvasP0 with { Y = canvasP0.Y + y }, canvasP1 with { Y = canvasP0.Y + y }, 0x28C8C8C8);
|
||||
}
|
||||
drawList.PopClipRect();
|
||||
|
||||
drawList.PushClipRect(canvasP0, canvasP1, true);
|
||||
{
|
||||
var relativeMiddle = origin + absoluteMiddle;
|
||||
var ratio = Math.Min(canvasSize.X / Width, canvasSize.Y / Height) * 0.95f * _scrolling.Z;
|
||||
var size = new Vector2(Width, Height) * ratio / 2f;
|
||||
|
||||
drawList.AddImage(GetPointer(), relativeMiddle - size, relativeMiddle + size);
|
||||
drawList.AddRect(relativeMiddle - size, relativeMiddle + size, 0xFFFFFFFF);
|
||||
}
|
||||
drawList.PopClipRect();
|
||||
}
|
||||
}
|
||||
|
||||
public enum TextureType
|
||||
|
|
|
|||
|
|
@ -1,99 +1,66 @@
|
|||
using CUE4Parse.UE4.Assets.Exports.Texture;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
|
||||
namespace FModel.Views.Snooper.Shading;
|
||||
|
||||
public static class TextureHelper
|
||||
{
|
||||
private static readonly string _game = Services.ApplicationService.ApplicationView.CUE4Parse.Provider.GameName;
|
||||
|
||||
/// <summary>
|
||||
/// Red : Specular (if possible)
|
||||
/// Blue : Roughness
|
||||
/// Red : Specular (not used anymore)
|
||||
/// Green : Metallic
|
||||
/// Blue : Roughness
|
||||
/// </summary>
|
||||
public static void FixChannels(UTexture2D o, FTexture2DMipMap mip, ref byte[] data)
|
||||
public static void FixChannels(string game, Texture texture)
|
||||
{
|
||||
// only if it makes a big difference pls
|
||||
switch (_game)
|
||||
switch (game)
|
||||
{
|
||||
case "hk_project":
|
||||
case "gameface":
|
||||
case "divineknockout":
|
||||
// R: Whatever (AO / S / E / ...)
|
||||
// G: Roughness
|
||||
// B: Metallic
|
||||
case "HK_PROJECT":
|
||||
case "COSMICSHAKE":
|
||||
case "PHOENIX":
|
||||
case "ATOMICHEART":
|
||||
{
|
||||
unsafe
|
||||
texture.SwizzleMask = new []
|
||||
{
|
||||
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]); // RBG
|
||||
offset += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
(int) PixelFormat.Red,
|
||||
(int) PixelFormat.Blue,
|
||||
(int) PixelFormat.Green,
|
||||
(int) PixelFormat.Alpha
|
||||
};
|
||||
break;
|
||||
}
|
||||
// R: Metallic
|
||||
// G: Roughness
|
||||
// B: Whatever (AO / S / E / ...)
|
||||
case "SHOOTERGAME":
|
||||
case "DIVINEKNOCKOUT":
|
||||
case "MOONMAN":
|
||||
{
|
||||
texture.SwizzleMask = new []
|
||||
{
|
||||
(int) PixelFormat.Blue,
|
||||
(int) PixelFormat.Red,
|
||||
(int) PixelFormat.Green,
|
||||
(int) PixelFormat.Alpha
|
||||
};
|
||||
break;
|
||||
}
|
||||
// R: Roughness
|
||||
// G: Metallic
|
||||
// B: Whatever (AO / S / E / ...)
|
||||
case "ccff7r":
|
||||
case "CCFF7R":
|
||||
{
|
||||
unsafe
|
||||
texture.SwizzleMask = new []
|
||||
{
|
||||
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]); // RBG
|
||||
(d[offset], d[offset + 1]) = (d[offset + 1], d[offset]); // BRG
|
||||
offset += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "shootergame":
|
||||
{
|
||||
var packedPBRType = o.Name[(o.Name.LastIndexOf('_') + 1)..];
|
||||
switch (packedPBRType)
|
||||
{
|
||||
case "MRAE": // R: Metallic, G: Roughness, B: AO (0-127) & Emissive (128-255) (Character PBR)
|
||||
unsafe
|
||||
{
|
||||
var offset = 0;
|
||||
fixed (byte* d = data)
|
||||
{
|
||||
for (var i = 0; i < mip.SizeX * mip.SizeY; i++)
|
||||
{
|
||||
(d[offset], d[offset + 1]) = (d[offset + 1], d[offset]); // RMAE
|
||||
// (d[offset], d[offset + 2]) = (d[offset + 2], d[offset]); // AEMR
|
||||
offset += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "MRAS": // R: Metallic, G: Roughness, B: AO, A: Specular (Legacy PBR)
|
||||
case "MRA": // R: Metallic, G: 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]); // SRM
|
||||
(d[offset + 1], d[offset + 2]) = (d[offset + 2], d[offset + 1]); // SMR
|
||||
offset += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
(int) PixelFormat.Blue,
|
||||
(int) PixelFormat.Green,
|
||||
(int) PixelFormat.Red,
|
||||
(int) PixelFormat.Alpha
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
texture.Swizzle();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using OpenTK.Windowing.Common;
|
|||
using System.Numerics;
|
||||
using System.Text;
|
||||
using FModel.Settings;
|
||||
using FModel.Views.Snooper.Animations;
|
||||
using FModel.Views.Snooper.Models;
|
||||
using FModel.Views.Snooper.Shading;
|
||||
using OpenTK.Graphics.OpenGL4;
|
||||
|
|
@ -16,12 +17,23 @@ namespace FModel.Views.Snooper;
|
|||
|
||||
public class Swap
|
||||
{
|
||||
public string Title;
|
||||
public string Description;
|
||||
public bool Value;
|
||||
public bool IsAware;
|
||||
public Action Content;
|
||||
|
||||
public Swap()
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Title = string.Empty;
|
||||
Description = string.Empty;
|
||||
Value = false;
|
||||
Content = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -31,6 +43,11 @@ public class Save
|
|||
public string Label;
|
||||
public string Path;
|
||||
|
||||
public Save()
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Value = false;
|
||||
|
|
@ -46,8 +63,10 @@ public class SnimGui
|
|||
private readonly Save _saver = new ();
|
||||
private readonly string _renderer;
|
||||
private readonly string _version;
|
||||
private readonly float _tableWidth;
|
||||
|
||||
private Vector2 _outlinerSize;
|
||||
private bool _ti_open;
|
||||
private bool _ti_overlayUv;
|
||||
private bool _viewportFocus;
|
||||
|
||||
private readonly Vector4 _accentColor = new (0.125f, 0.42f, 0.831f, 1.0f);
|
||||
|
|
@ -58,9 +77,12 @@ public class SnimGui
|
|||
|
||||
public SnimGui(int width, int height)
|
||||
{
|
||||
Controller = new ImGuiController(width, height);
|
||||
|
||||
_renderer = GL.GetString(StringName.Renderer);
|
||||
_version = "OpenGL " + GL.GetString(StringName.Version);
|
||||
Controller = new ImGuiController(width, height);
|
||||
_tableWidth = 17 * Controller.DpiScale;
|
||||
|
||||
Theme();
|
||||
}
|
||||
|
||||
|
|
@ -70,37 +92,99 @@ public class SnimGui
|
|||
DrawDockSpace(s.Size);
|
||||
|
||||
SectionWindow("Material Inspector", s.Renderer, DrawMaterialInspector, false);
|
||||
AnimationWindow("Timeline", s.Renderer, (icons, tracker, animations) =>
|
||||
tracker.ImGuiTimeline(s, _saver, icons, animations, _outlinerSize, Controller.FontSemiBold));
|
||||
|
||||
// Window("Timeline", () => {});
|
||||
Window("World", () => DrawWorld(s), false);
|
||||
Window("Sockets", () => DrawSockets(s));
|
||||
|
||||
DrawSockets(s);
|
||||
DrawOuliner(s);
|
||||
DrawDetails(s);
|
||||
Draw3DViewport(s);
|
||||
DrawNavbar();
|
||||
|
||||
DrawModals(s);
|
||||
|
||||
if (_ti_open) DrawTextureInspector(s);
|
||||
Controller.Render();
|
||||
}
|
||||
|
||||
private void DrawModals(Snooper s)
|
||||
{
|
||||
Modal(_swapper.Title, _swapper.Value, () =>
|
||||
{
|
||||
ImGui.TextWrapped(_swapper.Description);
|
||||
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.Content();
|
||||
_swapper.Reset();
|
||||
ImGui.CloseCurrentPopup();
|
||||
s.WindowShouldClose(true, false);
|
||||
}
|
||||
|
||||
ImGui.SetItemDefaultFocus();
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button("Cancel", size))
|
||||
{
|
||||
_swapper.Reset();
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
});
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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"))
|
||||
var length = s.Renderer.Options.Models.Count;
|
||||
|
||||
NoFramePaddingOnY(() =>
|
||||
{
|
||||
foreach (var model in s.Renderer.Options.Models.Values)
|
||||
Layout("Renderer");ImGui.Text($" : {_renderer}");
|
||||
Layout("Version");ImGui.Text($" : {_version}");
|
||||
Layout("Loaded Models");ImGui.Text($" : x{length}");ImGui.SameLine();
|
||||
|
||||
if (ImGui.SmallButton("Save All"))
|
||||
{
|
||||
b |= model.TrySave(out _, out _);
|
||||
foreach (var model in s.Renderer.Options.Models.Values)
|
||||
{
|
||||
b |= s.Renderer.Options.TrySave(model.Export, out _, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Modal("Saved", b, () =>
|
||||
{
|
||||
|
|
@ -137,9 +221,13 @@ public class SnimGui
|
|||
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();Layout("Animate With Rotation Only");ImGui.PushID(4);
|
||||
ImGui.Checkbox("", ref s.Renderer.AnimateWithRotationOnly);
|
||||
ImGui.PopID();Layout("Vertex Colors");ImGui.PushID(5);
|
||||
var c = (int) s.Renderer.Color;
|
||||
ImGui.Combo("vertex_colors", ref c,
|
||||
"Default\0Sections\0Colors\0Normals\0Texture Coordinates\0");
|
||||
s.Renderer.Color = (VertexColor) c;
|
||||
ImGui.PopID();
|
||||
|
||||
ImGui.EndTable();
|
||||
|
|
@ -149,7 +237,7 @@ public class SnimGui
|
|||
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
|
||||
if (ImGui.CollapsingHeader("Camera"))
|
||||
{
|
||||
s.Camera.ImGuiCamera();
|
||||
s.Renderer.CameraOp.ImGuiCamera();
|
||||
}
|
||||
|
||||
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
|
||||
|
|
@ -157,11 +245,14 @@ public class SnimGui
|
|||
{
|
||||
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}";
|
||||
var light = s.Renderer.Options.Lights[i];
|
||||
var id = s.Renderer.Options.TryGetModel(light.Model, out var lightModel) ? lightModel.Name : "None";
|
||||
|
||||
id += $"##{i}";
|
||||
if (ImGui.TreeNode(id) && ImGui.BeginTable(id, 2))
|
||||
{
|
||||
s.Renderer.Options.SelectModel(s.Renderer.Options.Lights[i].Model);
|
||||
s.Renderer.Options.Lights[i].ImGuiLight();
|
||||
s.Renderer.Options.SelectModel(light.Model);
|
||||
light.ImGuiLight();
|
||||
ImGui.EndTable();
|
||||
ImGui.TreePop();
|
||||
}
|
||||
|
|
@ -196,7 +287,7 @@ public class SnimGui
|
|||
|
||||
1. UI / UX
|
||||
- Press Shift while moving a window to dock it
|
||||
- Ctrl Click in a box to input a new value
|
||||
- Double 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
|
||||
|
||||
|
|
@ -204,6 +295,7 @@ public class SnimGui
|
|||
- WASD to move around
|
||||
- Shift to move faster
|
||||
- XC to zoom
|
||||
- Z to animate the selected model
|
||||
- Left Mouse Button pressed to look around
|
||||
- Right Click to select a model in the world
|
||||
|
||||
|
|
@ -216,7 +308,7 @@ public class SnimGui
|
|||
- Teleport to quickly move the camera to the position of the model
|
||||
- Delete
|
||||
- Deselect
|
||||
- Copy Name to Clipboard
|
||||
- Copy Path to Clipboard
|
||||
|
||||
4. World
|
||||
- Save All to save all loaded models at once
|
||||
|
|
@ -226,11 +318,19 @@ public class SnimGui
|
|||
5.1. Right Click Section
|
||||
- Show / Hide the section
|
||||
- Swap to change the material used by this section
|
||||
- Copy Name to Clipboard
|
||||
- Copy Path 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
|
||||
|
||||
6. Timeline
|
||||
- Press Space to play/pause
|
||||
- Control the time with your mouse
|
||||
6.1 Right Click Section
|
||||
- Animate another loaded model
|
||||
- Save
|
||||
- Copy Path to Clipboard
|
||||
");
|
||||
ImGui.Separator();
|
||||
|
||||
|
|
@ -299,11 +399,13 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
|||
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
|
||||
Window("Outliner", () =>
|
||||
{
|
||||
if (ImGui.BeginTable("Items", 3, ImGuiTableFlags.Resizable | ImGuiTableFlags.BordersOuterV, ImGui.GetContentRegionAvail()))
|
||||
_outlinerSize = ImGui.GetWindowSize();
|
||||
if (ImGui.BeginTable("Items", 4, ImGuiTableFlags.Resizable | ImGuiTableFlags.BordersOuterV | ImGuiTableFlags.NoSavedSettings, ImGui.GetContentRegionAvail()))
|
||||
{
|
||||
ImGui.TableSetupColumn("Instance", ImGuiTableColumnFlags.NoHeaderWidth | ImGuiTableColumnFlags.WidthFixed);
|
||||
ImGui.TableSetupColumn("Channels", ImGuiTableColumnFlags.WidthFixed);
|
||||
ImGui.TableSetupColumn("Name");
|
||||
ImGui.TableSetupColumn("Instance", ImGuiTableColumnFlags.NoHeaderWidth | ImGuiTableColumnFlags.WidthFixed, _tableWidth);
|
||||
ImGui.TableSetupColumn("Channels", ImGuiTableColumnFlags.NoHeaderWidth | ImGuiTableColumnFlags.WidthFixed, _tableWidth);
|
||||
ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthStretch);
|
||||
ImGui.TableSetupColumn("", ImGuiTableColumnFlags.NoHeaderWidth | ImGuiTableColumnFlags.WidthFixed, _tableWidth);
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
var i = 0;
|
||||
|
|
@ -313,9 +415,11 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
|||
ImGui.TableNextRow();
|
||||
ImGui.TableNextColumn();
|
||||
if (!model.Show)
|
||||
{
|
||||
ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, ImGui.GetColorU32(new Vector4(1, 0, 0, .5f)));
|
||||
}
|
||||
else if (model.IsAttachment)
|
||||
ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, ImGui.GetColorU32(new Vector4(0, .75f, 0, .5f)));
|
||||
else if (model.IsAttached)
|
||||
ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, ImGui.GetColorU32(new Vector4(1, 1, 0, .5f)));
|
||||
|
||||
ImGui.Text(model.TransformsCount.ToString("D"));
|
||||
ImGui.TableNextColumn();
|
||||
|
|
@ -325,68 +429,58 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
|||
{
|
||||
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"))
|
||||
if (ImGui.MenuItem("Save"))
|
||||
{
|
||||
s.WindowShouldFreeze(true);
|
||||
_saver.Value = model.TrySave(out _saver.Label, out _saver.Path);
|
||||
_saver.Value = s.Renderer.Options.TrySave(model.Export, out _saver.Label, out _saver.Path);
|
||||
s.WindowShouldFreeze(false);
|
||||
}
|
||||
|
||||
ImGui.BeginDisabled(true);
|
||||
// ImGui.BeginDisabled(!model.HasSkeleton);
|
||||
if (ImGui.Selectable("Animate"))
|
||||
if (ImGui.MenuItem("Animate", model.HasSkeleton))
|
||||
{
|
||||
s.Renderer.Options.AnimateMesh(true);
|
||||
s.WindowShouldClose(true, false);
|
||||
if (_swapper.IsAware)
|
||||
{
|
||||
s.Renderer.Options.RemoveAnimations();
|
||||
s.Renderer.Options.AnimateMesh(true);
|
||||
s.WindowShouldClose(true, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_swapper.Title = "Skeletal Animation";
|
||||
_swapper.Description = "You're about to animate a model.\nThe window will close for you to extract an animation!\n\n";
|
||||
_swapper.Content = () =>
|
||||
{
|
||||
s.Renderer.Options.RemoveAnimations();
|
||||
s.Renderer.Options.AnimateMesh(true);
|
||||
};
|
||||
_swapper.Value = true;
|
||||
}
|
||||
}
|
||||
if (ImGui.MenuItem("Teleport To"))
|
||||
{
|
||||
var instancePos = model.Transforms[model.SelectedInstance].Matrix.Translation;
|
||||
s.Renderer.CameraOp.Teleport(instancePos, model.Box);
|
||||
}
|
||||
|
||||
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);
|
||||
if (ImGui.MenuItem("Delete")) s.Renderer.Options.RemoveModel(guid);
|
||||
if (ImGui.MenuItem("Deselect")) s.Renderer.Options.SelectModel(Guid.Empty);
|
||||
ImGui.Separator();
|
||||
if (ImGui.Selectable("Copy Name to Clipboard")) ImGui.SetClipboardText(model.Name);
|
||||
if (ImGui.MenuItem("Copy Path to Clipboard")) ImGui.SetClipboardText(model.Path);
|
||||
});
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Image(s.Renderer.Options.Icons[model.AttachIcon].GetPointer(), new Vector2(_tableWidth));
|
||||
TooltipCopy(model.AttachTooltip);
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
|
@ -395,30 +489,37 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
|||
|
||||
private void DrawSockets(Snooper s)
|
||||
{
|
||||
foreach (var model in s.Renderer.Options.Models.Values)
|
||||
MeshWindow("Sockets", s.Renderer, (icons, selectedModel) =>
|
||||
{
|
||||
if (!model.HasSkeleton || model.IsSelected) return;
|
||||
if (ImGui.TreeNode($"{model.Name} [{model.Skeleton.Sockets.Length}]"))
|
||||
var info = new SocketAttachementInfo { Guid = s.Renderer.Options.SelectedModel, Instance = selectedModel.SelectedInstance };
|
||||
foreach (var model in s.Renderer.Options.Models.Values)
|
||||
{
|
||||
var i = 0;
|
||||
foreach (var socket in model.Skeleton.Sockets)
|
||||
if (!model.HasSockets || model.IsSelected) continue;
|
||||
if (ImGui.TreeNode($"{model.Name} [{model.Sockets.Count}]"))
|
||||
{
|
||||
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))
|
||||
var i = 0;
|
||||
foreach (var socket in model.Sockets)
|
||||
{
|
||||
selected.Transforms[selected.SelectedInstance] = socket.Transform;
|
||||
selected.UpdateMatrix(selected.SelectedInstance);
|
||||
var isAttached = socket.AttachedModels.Contains(info);
|
||||
ImGui.PushID(i);
|
||||
ImGui.BeginDisabled(selectedModel.IsAttached && !isAttached);
|
||||
switch (isAttached)
|
||||
{
|
||||
case false when ImGui.Button($"Attach to '{socket.Name}'"):
|
||||
selectedModel.AttachModel(model, socket, info);
|
||||
break;
|
||||
case true when ImGui.Button($"Detach from '{socket.Name}'"):
|
||||
selectedModel.DetachModel(model, socket, info);
|
||||
break;
|
||||
}
|
||||
ImGui.EndDisabled();
|
||||
ImGui.PopID();
|
||||
i++;
|
||||
}
|
||||
ImGui.PopID();
|
||||
i++;
|
||||
ImGui.TreePop();
|
||||
}
|
||||
ImGui.TreePop();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void DrawDetails(Snooper s)
|
||||
|
|
@ -428,22 +529,29 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
|||
{
|
||||
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)
|
||||
NoFramePaddingOnY(() =>
|
||||
{
|
||||
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}");
|
||||
}
|
||||
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.Name}");
|
||||
Layout("Bones");ImGui.Text($" : x{model.Skeleton.BoneCount}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Layout("Two Sided");ImGui.Text($" : {model.TwoSided}");
|
||||
}
|
||||
Layout("Sockets");ImGui.Text($" : x{model.Sockets.Count}");
|
||||
|
||||
ImGui.EndTable();
|
||||
ImGui.EndTable();
|
||||
});
|
||||
}
|
||||
if (ImGui.BeginTabBar("tabbar_details", ImGuiTabBarFlags.None))
|
||||
{
|
||||
if (ImGui.BeginTabItem("Sections") && ImGui.BeginTable("table_sections", 2, ImGuiTableFlags.Resizable | ImGuiTableFlags.BordersOuterV, ImGui.GetContentRegionAvail()))
|
||||
if (ImGui.BeginTabItem("Sections") && ImGui.BeginTable("table_sections", 2, ImGuiTableFlags.Resizable | ImGuiTableFlags.BordersOuterV | ImGuiTableFlags.NoSavedSettings, ImGui.GetContentRegionAvail()))
|
||||
{
|
||||
ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.WidthFixed);
|
||||
ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.NoHeaderWidth | ImGuiTableColumnFlags.WidthFixed, _tableWidth);
|
||||
ImGui.TableSetupColumn("Material");
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
|
|
@ -459,6 +567,10 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
|||
{
|
||||
ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, ImGui.GetColorU32(new Vector4(1, 0, 0, .5f)));
|
||||
}
|
||||
else if (s.Renderer.Color == VertexColor.Sections)
|
||||
{
|
||||
ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, ImGui.GetColorU32(new Vector4(section.Color, 0.5f)));
|
||||
}
|
||||
|
||||
ImGui.Text(section.MaterialIndex.ToString("D"));
|
||||
ImGui.TableNextColumn();
|
||||
|
|
@ -470,50 +582,28 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
|||
{
|
||||
s.Renderer.Options.SelectSection(i);
|
||||
if (ImGui.MenuItem("Show", null, section.Show)) section.Show = !section.Show;
|
||||
if (ImGui.Selectable("Swap"))
|
||||
if (ImGui.MenuItem("Swap"))
|
||||
{
|
||||
if (_swapper.IsAware)
|
||||
{
|
||||
s.Renderer.Options.SwapMaterial(true);
|
||||
s.WindowShouldClose(true, false);
|
||||
}
|
||||
else _swapper.Value = true;
|
||||
else
|
||||
{
|
||||
_swapper.Title = "Material Swap";
|
||||
_swapper.Description = "You're about to swap a material.\nThe window will close for you to extract a material!\n\n";
|
||||
_swapper.Content = () => s.Renderer.Options.SwapMaterial(true);
|
||||
_swapper.Value = true;
|
||||
}
|
||||
}
|
||||
ImGui.Separator();
|
||||
if (ImGui.Selectable("Copy Name to Clipboard")) ImGui.SetClipboardText(material.Name);
|
||||
if (ImGui.MenuItem("Copy Path to Clipboard")) ImGui.SetClipboardText(material.Path);
|
||||
});
|
||||
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();
|
||||
}
|
||||
|
||||
|
|
@ -524,8 +614,8 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
|||
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);
|
||||
model.Transforms[model.SelectedInstance].ImGuiTransform(s.Renderer.CameraOp.Speed / 100f);
|
||||
|
||||
ImGui.EndTabItem();
|
||||
}
|
||||
|
||||
|
|
@ -539,7 +629,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
|||
|
||||
if (ImGui.BeginListBox("", box))
|
||||
{
|
||||
for (int i = 0; i < model.Morphs.Length; i++)
|
||||
for (int i = 0; i < model.Morphs.Count; i++)
|
||||
{
|
||||
ImGui.PushID(i);
|
||||
if (ImGui.Selectable(model.Morphs[i].Name, s.Renderer.Options.SelectedMorph == i))
|
||||
|
|
@ -591,29 +681,39 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
|||
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
|
||||
if (ImGui.CollapsingHeader("Properties"))
|
||||
{
|
||||
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
|
||||
if (ImGui.TreeNode("Scalars"))
|
||||
NoFramePaddingOnY(() =>
|
||||
{
|
||||
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();
|
||||
}
|
||||
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
|
||||
if (ImGui.TreeNode("Base"))
|
||||
{
|
||||
material.ImGuiBaseProperties("base");
|
||||
ImGui.TreePop();
|
||||
}
|
||||
|
||||
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("Switches"))
|
||||
{
|
||||
material.ImGuiDictionaries("switches", material.Parameters.Switches, true);
|
||||
ImGui.TreePop();
|
||||
}
|
||||
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
|
||||
if (ImGui.TreeNode("Colors"))
|
||||
{
|
||||
material.ImGuiColors(material.Parameters.Colors);
|
||||
ImGui.TreePop();
|
||||
}
|
||||
if (ImGui.TreeNode("All Textures"))
|
||||
{
|
||||
material.ImGuiDictionaries("textures", material.Parameters.Textures);
|
||||
ImGui.TreePop();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -623,22 +723,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
|||
s.Renderer.Options.TryGetModel(out var model) &&
|
||||
s.Renderer.Options.TryGetSection(model, out var section))
|
||||
{
|
||||
var vectors = model.Materials[section.MaterialIndex].ImGuiTextureInspector(s.Renderer.Options.Icons["noimage"]);
|
||||
if (_ti_overlayUv)
|
||||
{
|
||||
var size = vectors[0];
|
||||
var drawList = ImGui.GetWindowDrawList();
|
||||
drawList.PushClipRect(size, size, true);
|
||||
ImGui.SetCursorPos(vectors[1]);
|
||||
ImGui.InvisibleButton("canvas", size, ImGuiButtonFlags.MouseButtonLeft | ImGuiButtonFlags.MouseButtonRight);
|
||||
drawList.AddLine(new Vector2(0, 0), size, 255, 2f);
|
||||
drawList.PopClipRect();
|
||||
}
|
||||
Popup(() =>
|
||||
{
|
||||
if (ImGui.MenuItem("Overlay UVs", null, _ti_overlayUv, false))
|
||||
_ti_overlayUv = !_ti_overlayUv;
|
||||
});
|
||||
(model.Materials[section.MaterialIndex].GetSelectedTexture() ?? s.Renderer.Options.Icons["noimage"]).ImGuiTextureInspector();
|
||||
}
|
||||
ImGui.End(); // if window is collapsed
|
||||
}
|
||||
|
|
@ -653,8 +738,8 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
|||
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);
|
||||
s.Renderer.CameraOp.AspectRatio = size.X / size.Y;
|
||||
ImGui.Image(s.Framebuffer.GetPointer(), size, new Vector2(0, 1), new Vector2(1, 0), Vector4.One);
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
|
|
@ -669,15 +754,13 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
|||
var guid = s.Renderer.Picking.ReadPixel(ImGui.GetMousePos(), ImGui.GetCursorScreenPos(), size);
|
||||
s.Renderer.Options.SelectModel(guid);
|
||||
ImGui.SetWindowFocus("Outliner");
|
||||
ImGui.SetWindowFocus("Details");
|
||||
}
|
||||
}
|
||||
|
||||
const float lookSensitivity = 0.1f;
|
||||
if (ImGui.IsMouseDragging(ImGuiMouseButton.Left, lookSensitivity) && _viewportFocus)
|
||||
if (ImGui.IsMouseDragging(ImGuiMouseButton.Left) && _viewportFocus)
|
||||
{
|
||||
var io = ImGui.GetIO();
|
||||
var delta = io.MouseDelta * lookSensitivity;
|
||||
s.Camera.ModifyDirection(delta.X, delta.Y);
|
||||
s.Renderer.CameraOp.Modify(ImGui.GetIO().MouseDelta);
|
||||
}
|
||||
|
||||
// if left button up and mouse was in viewport
|
||||
|
|
@ -690,11 +773,15 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
|||
float framerate = ImGui.GetIO().Framerate;
|
||||
ImGui.SetCursorPos(size with { X = 7.5f });
|
||||
ImGui.Text($"FPS: {framerate:0} ({1000.0f / framerate:0.##} ms)");
|
||||
|
||||
const string label = "Previewed content may differ from final version saved or used in-game.";
|
||||
ImGui.SetCursorPos(size with { X = size.X - ImGui.CalcTextSize(label).X - 7.5f });
|
||||
ImGui.TextColored(new Vector4(0.50f, 0.50f, 0.50f, 1.00f), label);
|
||||
}, false);
|
||||
ImGui.PopStyleVar();
|
||||
}
|
||||
|
||||
private void Popup(Action content)
|
||||
public static void Popup(Action content)
|
||||
{
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, new Vector2(4f));
|
||||
if (ImGui.BeginPopupContextItem())
|
||||
|
|
@ -749,6 +836,13 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
|||
}, styled);
|
||||
}
|
||||
|
||||
private void AnimationWindow(string name, Renderer renderer, Action<Dictionary<string, Texture>, TimeTracker, List<Animation>> content, bool styled = true)
|
||||
{
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
|
||||
Window(name, () => content(renderer.Options.Icons, renderer.Options.Tracker, renderer.Options.Animations), styled);
|
||||
ImGui.PopStyleVar();
|
||||
}
|
||||
|
||||
private void PopStyleCompact() => ImGui.PopStyleVar(2);
|
||||
private void PushStyleCompact()
|
||||
{
|
||||
|
|
@ -756,6 +850,13 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
|||
ImGui.PushStyleVar(ImGuiStyleVar.CellPadding, new Vector2(0, 1));
|
||||
}
|
||||
|
||||
public static void NoFramePaddingOnY(Action content)
|
||||
{
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, new Vector2(8, 0));
|
||||
content();
|
||||
ImGui.PopStyleVar();
|
||||
}
|
||||
|
||||
private void NoMeshSelected() => CenteredTextColored(_errorColor, "No Mesh Selected");
|
||||
private void NoSectionSelected() => CenteredTextColored(_errorColor, "No Section Selected");
|
||||
private void CenteredTextColored(Vector4 color, string text)
|
||||
|
|
@ -774,6 +875,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
|||
{
|
||||
ImGui.TableNextRow();
|
||||
ImGui.TableSetColumnIndex(0);
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.Spacing();ImGui.SameLine();ImGui.Text(name);
|
||||
if (tooltip) TooltipCopy(name);
|
||||
ImGui.TableSetColumnIndex(1);
|
||||
|
|
@ -798,7 +900,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
|
|||
io.ConfigFlags |= ImGuiConfigFlags.DockingEnable;
|
||||
io.ConfigFlags |= ImGuiConfigFlags.ViewportsEnable;
|
||||
io.ConfigWindowsMoveFromTitleBarOnly = true;
|
||||
io.ConfigDockingWithShift = true;
|
||||
// io.ConfigDockingWithShift = true; doesn't work anymore??
|
||||
|
||||
style.WindowPadding = new Vector2(4f);
|
||||
style.FramePadding = new Vector2(3f);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using CUE4Parse.UE4.Assets.Exports;
|
||||
using FModel.Views.Snooper.Buffers;
|
||||
using ImGuiNET;
|
||||
|
|
@ -14,24 +13,22 @@ using OpenTK.Windowing.Desktop;
|
|||
using OpenTK.Windowing.GraphicsLibraryFramework;
|
||||
using SixLabors.ImageSharp.Advanced;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using Application = System.Windows.Application;
|
||||
using Keys = OpenTK.Windowing.GraphicsLibraryFramework.Keys;
|
||||
|
||||
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);
|
||||
|
||||
|
|
@ -41,13 +38,7 @@ public class Snooper : GameWindow
|
|||
|
||||
public bool TryLoadExport(CancellationToken cancellationToken, UObject export)
|
||||
{
|
||||
var newCamera = Renderer.Load(cancellationToken, export) ?? new Camera();
|
||||
if (newCamera.Speed > _previousSpeed)
|
||||
{
|
||||
newCamera.Zoom = Camera.Zoom;
|
||||
Camera = newCamera;
|
||||
_previousSpeed = Camera.Speed;
|
||||
}
|
||||
Renderer.Load(cancellationToken, export);
|
||||
return Renderer.Options.Models.Count > 0;
|
||||
}
|
||||
|
||||
|
|
@ -55,9 +46,7 @@ public class Snooper : GameWindow
|
|||
{
|
||||
if (clear)
|
||||
{
|
||||
_previousSpeed = 0f;
|
||||
Renderer.Options.ResetModelsAndLights();
|
||||
Renderer.Options.SelectModel(Guid.Empty);
|
||||
Renderer.CameraOp.Speed = 0;
|
||||
Renderer.Save();
|
||||
}
|
||||
|
||||
|
|
@ -73,6 +62,8 @@ public class Snooper : GameWindow
|
|||
|
||||
public override void Run()
|
||||
{
|
||||
Renderer.Options.SwapMaterial(false);
|
||||
Renderer.Options.AnimateMesh(false);
|
||||
Application.Current.Dispatcher.Invoke(delegate
|
||||
{
|
||||
WindowShouldClose(false, false);
|
||||
|
|
@ -82,7 +73,7 @@ public class Snooper : GameWindow
|
|||
|
||||
private unsafe void LoadWindowIcon()
|
||||
{
|
||||
var info = Application.GetResourceStream(new Uri($"/FModel;component/Resources/engine.png", UriKind.Relative));
|
||||
var info = Application.GetResourceStream(new Uri("/FModel;component/Resources/engine.png", UriKind.Relative));
|
||||
using var img = SixLabors.ImageSharp.Image.Load<Rgba32>(info.Stream);
|
||||
var memoryGroup = img.GetPixelMemoryGroup();
|
||||
Memory<byte> array = new byte[memoryGroup.TotalLength * sizeof(Rgba32)];
|
||||
|
|
@ -110,6 +101,7 @@ public class Snooper : GameWindow
|
|||
|
||||
GL.ClearColor(OpenTK.Mathematics.Color4.Black);
|
||||
GL.Enable(EnableCap.Blend);
|
||||
GL.Enable(EnableCap.CullFace);
|
||||
GL.Enable(EnableCap.DepthTest);
|
||||
GL.Enable(EnableCap.Multisample);
|
||||
GL.StencilOp(StencilOp.Keep, StencilOp.Replace, StencilOp.Replace);
|
||||
|
|
@ -126,14 +118,16 @@ public class Snooper : GameWindow
|
|||
if (!IsVisible)
|
||||
return;
|
||||
|
||||
var delta = (float) args.Time;
|
||||
|
||||
ClearWhatHasBeenDrawn(); // clear window background
|
||||
_gui.Controller.Update(this, (float)args.Time);
|
||||
_gui.Controller.Update(this, delta);
|
||||
_gui.Render(this);
|
||||
|
||||
Framebuffer.Bind(); // switch to viewport background
|
||||
ClearWhatHasBeenDrawn(); // clear viewport background
|
||||
|
||||
Renderer.Render(Camera);
|
||||
Renderer.Render(delta);
|
||||
|
||||
Framebuffer.BindMsaa();
|
||||
Framebuffer.Bind(0); // switch to window background
|
||||
|
|
@ -162,25 +156,21 @@ public class Snooper : GameWindow
|
|||
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);
|
||||
var delta = (float) e.Time;
|
||||
Renderer.CameraOp.Modify(KeyboardState, delta);
|
||||
|
||||
if (KeyboardState.IsKeyPressed(Keys.Z) &&
|
||||
Renderer.Options.TryGetModel(out var selectedModel) &&
|
||||
selectedModel.HasSkeleton)
|
||||
{
|
||||
Renderer.Options.RemoveAnimations();
|
||||
Renderer.Options.AnimateMesh(true);
|
||||
WindowShouldClose(true, false);
|
||||
}
|
||||
if (KeyboardState.IsKeyPressed(Keys.Space))
|
||||
Renderer.Options.Tracker.IsPaused = !Renderer.Options.Tracker.IsPaused;
|
||||
if (KeyboardState.IsKeyPressed(Keys.Delete))
|
||||
Renderer.Options.RemoveModel(Renderer.Options.SelectedModel);
|
||||
if (KeyboardState.IsKeyPressed(Keys.H))
|
||||
WindowShouldClose(true, false);
|
||||
if (KeyboardState.IsKeyPressed(Keys.Escape))
|
||||
|
|
@ -193,9 +183,8 @@ public class Snooper : GameWindow
|
|||
|
||||
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);
|
||||
Renderer.WindowResized(e.Width, e.Height);
|
||||
|
||||
_gui.Controller.WindowResized(e.Width, e.Height);
|
||||
}
|
||||
|
|
@ -205,4 +194,62 @@ public class Snooper : GameWindow
|
|||
base.OnClosing(e);
|
||||
WindowShouldClose(true, true);
|
||||
}
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool EnumDisplaySettings(
|
||||
string deviceName, int modeNum, ref DEVMODE devMode);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct DEVMODE
|
||||
{
|
||||
private const int CCHDEVICENAME = 0x20;
|
||||
private const int CCHFORMNAME = 0x20;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)]
|
||||
public string dmDeviceName;
|
||||
public short dmSpecVersion;
|
||||
public short dmDriverVersion;
|
||||
public short dmSize;
|
||||
public short dmDriverExtra;
|
||||
public int dmFields;
|
||||
public int dmPositionX;
|
||||
public int dmPositionY;
|
||||
public ScreenOrientation dmDisplayOrientation;
|
||||
public int dmDisplayFixedOutput;
|
||||
public short dmColor;
|
||||
public short dmDuplex;
|
||||
public short dmYResolution;
|
||||
public short dmTTOption;
|
||||
public short dmCollate;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)]
|
||||
public string dmFormName;
|
||||
public short dmLogPixels;
|
||||
public int dmBitsPerPel;
|
||||
public int dmPelsWidth;
|
||||
public int dmPelsHeight;
|
||||
public int dmDisplayFlags;
|
||||
public int dmDisplayFrequency;
|
||||
public int dmICMMethod;
|
||||
public int dmICMIntent;
|
||||
public int dmMediaType;
|
||||
public int dmDitherType;
|
||||
public int dmReserved1;
|
||||
public int dmReserved2;
|
||||
public int dmPanningWidth;
|
||||
public int dmPanningHeight;
|
||||
|
||||
}
|
||||
|
||||
public static int GetMaxRefreshFrequency()
|
||||
{
|
||||
var rf = 60;
|
||||
var vDevMode = new DEVMODE();
|
||||
var i = 0;
|
||||
while (EnumDisplaySettings(null, i, ref vDevMode))
|
||||
{
|
||||
i++;
|
||||
rf = Math.Max(rf, vDevMode.dmDisplayFrequency);
|
||||
}
|
||||
|
||||
return rf;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,26 +12,23 @@ public class Transform
|
|||
}
|
||||
|
||||
public Matrix4x4 Relation = Matrix4x4.Identity;
|
||||
public FVector Position = FVector.ZeroVector.ToMapVector();
|
||||
public FRotator Rotation = new (0f);
|
||||
public FVector Scale = FVector.OneVector.ToMapVector();
|
||||
public FVector Position = FVector.ZeroVector;
|
||||
public FQuat Rotation = FQuat.Identity;
|
||||
public FVector Scale = FVector.OneVector;
|
||||
|
||||
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 Matrix4x4 LocalMatrix =>
|
||||
Matrix4x4.CreateScale(Scale.X, Scale.Z, Scale.Y) *
|
||||
Matrix4x4.CreateFromQuaternion(Quaternion.Normalize(new Quaternion(Rotation.X, Rotation.Z, Rotation.Y, -Rotation.W))) *
|
||||
Matrix4x4.CreateTranslation(Position.X, Position.Z, Position.Y);
|
||||
public Matrix4x4 Matrix => LocalMatrix * Relation;
|
||||
|
||||
public void ImGuiTransform(float speed)
|
||||
{
|
||||
const int width = 100;
|
||||
const float width = 100f;
|
||||
|
||||
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");
|
||||
|
||||
|
|
@ -41,30 +38,30 @@ public class Transform
|
|||
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.DragFloat("W", ref Rotation.W, .005f, 0f, 0f, "%.3f rad");
|
||||
|
||||
ImGui.SetNextItemWidth(width);
|
||||
ImGui.DragFloat("Y", ref Rotation.Pitch, .5f, 0f, 0f, "%.1f°");
|
||||
ImGui.DragFloat("X", ref Rotation.X, .005f, 0f, 0f, "%.3f rad");
|
||||
|
||||
ImGui.SetNextItemWidth(width);
|
||||
ImGui.DragFloat("Z", ref Rotation.Yaw, .5f, 0f, 0f, "%.1f°");
|
||||
ImGui.DragFloat("Y", ref Rotation.Z, .005f, 0f, 0f, "%.3f rad");
|
||||
|
||||
ImGui.SetNextItemWidth(width);
|
||||
ImGui.DragFloat("Z", ref Rotation.Y, .005f, 0f, 0f, "%.3f rad");
|
||||
|
||||
ImGui.PopID();
|
||||
ImGui.TreePop();
|
||||
}
|
||||
|
||||
ImGui.SetNextItemOpen(true, ImGuiCond.Appearing);
|
||||
if (ImGui.TreeNode("Scale"))
|
||||
{
|
||||
ImGui.PushID(3);
|
||||
ImGui.SetNextItemWidth(width);
|
||||
ImGui.DragFloat("X", ref Scale.X, speed, 0f, 0f, "%.3f");
|
||||
|
||||
|
|
@ -74,8 +71,9 @@ public class Transform
|
|||
ImGui.SetNextItemWidth(width);
|
||||
ImGui.DragFloat("Z", ref Scale.Y, speed, 0f, 0f, "%.3f");
|
||||
|
||||
ImGui.PopID();
|
||||
ImGui.TreePop();
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() => Matrix.Translation.ToString();
|
||||
}
|
||||
|
|
|
|||
292
NOTICE
Normal file
|
|
@ -0,0 +1,292 @@
|
|||
This software includes material developed by the following third-party libraries:
|
||||
|
||||
- Adonis UI (https://github.com/benruehl/adonis-ui/blob/master/LICENSE) - Copyright (c) 2018 Benjamin Rühl. Licensed under the MIT License.
|
||||
- AutoUpdater.NET (https://github.com/ravibpatel/AutoUpdater.NET/blob/master/LICENSE) - Copyright (c) 2012-2023 RBSoft. Licensed under the MIT License.
|
||||
- AvalonEdit (https://github.com/icsharpcode/AvalonEdit/blob/master/LICENSE) - Copyright (c) AvalonEdit Contributors. Licensed under the MIT License.
|
||||
- CSCore (https://github.com/filoe/cscore/blob/master/license.md) - Licensed under the MS-PL License.
|
||||
- CUE4Parse (https://github.com/FabianFG/CUE4Parse/blob/master/LICENSE) - Copyright 2023 CUE4Parse Contributors. Licensed under the Apache License 2.0.
|
||||
- DiscordRichPresence (https://github.com/Lachee/discord-rpc-csharp/blob/master/LICENSE) - Copyright (c) 2021 Lachee. Licensed under the MIT License.
|
||||
- EpicManifestParser (https://github.com/NotOfficer/EpicManifestParser/blob/master/LICENSE) - Copyright (c) 2020 NotOfficer. Licensed under the MIT License.
|
||||
- ImGui.NET (https://github.com/mellinoe/ImGui.NET/blob/master/LICENSE) - Copyright (c) 2017 Eric Mellino and ImGui.NET contributors. Licensed under the MIT License.
|
||||
- K4os.Compression.LZ4 (https://github.com/MiloszKrajewski/K4os.Compression.LZ4/blob/master/LICENSE) - Copyright (c) 2017 Milosz Krajewski. Licensed under the MIT License.
|
||||
- Newtonsoft.Json (https://github.com/JamesNK/Newtonsoft.Json/blob/master/LICENSE.md) - Copyright (c) 2007 James Newton-King. Licensed under the MIT License.
|
||||
- NVorbis (https://github.com/NVorbis/NVorbis/blob/master/LICENSE) - Copyright (c) 2020 Andrew Ward. Licensed under the MIT License.
|
||||
- Oodle.NET (https://github.com/NotOfficer/Oodle.NET/blob/master/LICENSE) - Copyright (c) 2021 Officer. Licensed under the MIT License.
|
||||
- Ookii.Dialogs.Wpf (https://github.com/ookii-dialogs/ookii-dialogs-wpf/blob/master/LICENSE) - Copyright (c) C. Augusto Proiete 2018-2021 & Sven Groot 2009-2018. Licensed under the BSD 3-Clause License.
|
||||
- OpenTK (https://github.com/opentk/opentk/blob/master/LICENSE.md) - Copyright (c) 2006-2019 Stefanos Apostolopoulos for the Open Toolkit project. Licensed under the MIT License.
|
||||
- RestSharp (https://github.com/restsharp/RestSharp/blob/dev/LICENSE.txt) - Copyright (c) .NET Foundation and Contributors. Licensed under the Apache License 2.0.
|
||||
- Serilog (https://github.com/serilog/serilog/blob/dev/LICENSE) - Copyright 2013-2015 Serilog Contributors. Licensed under the Apache License 2.0.
|
||||
- SixLabors.ImageSharp (https://github.com/SixLabors/ImageSharp/blob/main/LICENSE) - Copyright (c) Six Labors. Licensed under the Apache License 2.0.
|
||||
- SkiaSharp (https://github.com/mono/SkiaSharp/blob/main/LICENSE.md) - Copyright (c) 2015-2016 Xamarin, Inc. & 2017-2018 Microsoft Corporation. Licensed under the MIT License.
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
LICENSE TEXTS
|
||||
1. MIT License
|
||||
2. MS-PL License
|
||||
3. BSD 3-Clause License
|
||||
4. Apache License 2.0
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
MS-PL License
|
||||
|
||||
1. Definitions
|
||||
The terms “reproduce,” “reproduction,” “derivative works,” and “distribution” have the
|
||||
same meaning here as under U.S. copyright law.
|
||||
|
||||
A “contribution” is the original software, or any additions or changes to the software.
|
||||
|
||||
A “contributor” is any person that distributes its contribution under this license.
|
||||
|
||||
“Licensed patents” are a contributor’s patent claims that read directly on its contribution.
|
||||
|
||||
2. Grant of Rights
|
||||
|
||||
(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
|
||||
|
||||
(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software.
|
||||
|
||||
3. Conditions and Limitations
|
||||
|
||||
(A) No Trademark License- This license does not grant you rights to use any contributors’ name, logo, or trademarks.
|
||||
|
||||
(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically.
|
||||
|
||||
(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software.
|
||||
|
||||
(D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license.
|
||||
|
||||
(E) The software is licensed “as-is.” You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement.
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
BSD 3-Clause License
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
---------------------------------------------------------------------------
|
||||
41
README.md
|
|
@ -1,28 +1,33 @@
|
|||
# FModel [](https://discord.gg/fdkNYYQ)
|
||||
FModel - An Unreal Engine Archives Explorer in C#
|
||||
------------------------------------------
|
||||
|
||||
Open-source software for exploring Unreal Engine games.
|
||||
[](https://github.com/4sval/FModel/actions)
|
||||
[](https://fmodel.app/download)
|
||||
[](https://fmodel.app/donate)
|
||||
[](https://fmodel.app/discord)
|
||||
***
|
||||
|
||||
<img src="https://user-images.githubusercontent.com/26126862/119065662-52534800-b9de-11eb-85fd-a47797daa062.png" align="center" alt="FModel">
|
||||
### Description:
|
||||
FModel is an archive explorer for [Unreal Engine](https://www.unrealengine.com/en-US/) games that uses [CUE4Parse](https://github.com/FabianFG/CUE4Parse) as its core parsing library, providing robust support for the latest UE4 and UE5 archive formats. It aims to deliver a modern and intuitive user interface, powerful features, and a comprehensive set of tools for previewing and converting game packages, empowering YOU to understand games' inner workings with ease.
|
||||
|
||||
## Installation
|
||||
FModel is actively maintained and developed by a dedicated community of contributors, and welcomes all new contributions and feedback.
|
||||
|
||||
Read [our wiki](https://github.com/4sval/FModel/wiki)
|
||||
### Installation:
|
||||
For installation, follow the instructions from [here](https://github.com/4sval/FModel/wiki/Installing-FModel)
|
||||
|
||||
## Authors
|
||||
|
||||
- [@Asval](https://github.com/4sval)
|
||||
- [@Fabian](https://github.com/FabianFG)
|
||||
- [@amr](https://github.com/Amrsatrio)
|
||||
- [@GMatrix](https://github.com/GMatrixGames)
|
||||
- [@Tiger](https://github.com/XTigerHyperX)
|
||||
|
||||
## Support
|
||||
|
||||
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=EP9SSWG8MW4UC&source=url)
|
||||
|
||||
<p>This project is supported by:</p>
|
||||
### Sponsorship:
|
||||
<p>
|
||||
<a href="https://www.jetbrains.com/">
|
||||
<img src="https://cdn.fmodel.app/i/svg/jetbrains.svg" width="256px">
|
||||
</a>
|
||||
<a href="https://1password.com/">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://cdn.fmodel.app/i/svg/1password-light.svg">
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://cdn.fmodel.app/i/svg/1password-dark.svg">
|
||||
<img src="https://cdn.fmodel.app/i/svg/1password-light.svg" width="256px">
|
||||
</picture>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
### License:
|
||||
FModel is licensed under [GPL-3](https://github.com/4sval/FModel/blob/dev/LICENSE), and licenses of third-party libraries used are listed [here](https://github.com/4sval/FModel/blob/dev/NOTICE).
|
||||