diff --git a/CUE4Parse b/CUE4Parse
index 340b7567..691b9b93 160000
--- a/CUE4Parse
+++ b/CUE4Parse
@@ -1 +1 @@
-Subproject commit 340b7567fdc2ca050d9042389d09540be1044159
+Subproject commit 691b9b93ced53f723119106bc75094da77a4eaf3
diff --git a/FModel/Creator/Bases/FN/BaseBundle.cs b/FModel/Creator/Bases/FN/BaseBundle.cs
index ea85a775..90c9de2b 100644
--- a/FModel/Creator/Bases/FN/BaseBundle.cs
+++ b/FModel/Creator/Bases/FN/BaseBundle.cs
@@ -59,7 +59,7 @@ public class BaseBundle : UCreator
foreach (var reward in rewards)
{
if (!reward.TryGetValue(out FSoftObjectPath itemDefinition, "ItemDefinition")) continue;
- quest.AddCompletionRequest(itemDefinition);
+ quest.AddCompletionReward(itemDefinition);
}
_quests.Add(quest);
}
diff --git a/FModel/Creator/Bases/FN/BaseQuest.cs b/FModel/Creator/Bases/FN/BaseQuest.cs
index 48984aff..ef3159aa 100644
--- a/FModel/Creator/Bases/FN/BaseQuest.cs
+++ b/FModel/Creator/Bases/FN/BaseQuest.cs
@@ -44,12 +44,12 @@ public class BaseQuest : BaseIcon
DisplayName = ReformatString(description, completionCount.ToString(), completionCount < 0);
}
- public void AddCompletionRequest(FSoftObjectPath itemDefinition)
+ public void AddCompletionReward(FSoftObjectPath itemDefinition)
{
_rewards.Add(itemDefinition.TryLoad(out UObject uObject) ? new Reward(uObject) : new Reward());
}
- public void AddCompletionRequest(int quantity, string reward)
+ public void AddCompletionReward(int quantity, string reward)
{
_rewards.Add(new Reward(quantity, reward));
}
diff --git a/FModel/FModel.csproj b/FModel/FModel.csproj
index dc171282..bdfee340 100644
--- a/FModel/FModel.csproj
+++ b/FModel/FModel.csproj
@@ -164,7 +164,7 @@
-
+
diff --git a/FModel/MainWindow.xaml.cs b/FModel/MainWindow.xaml.cs
index 2f915233..d2f1ade5 100644
--- a/FModel/MainWindow.xaml.cs
+++ b/FModel/MainWindow.xaml.cs
@@ -63,7 +63,6 @@ public partial class MainWindow
await ApplicationViewModel.InitOodle();
await ApplicationViewModel.InitZlib();
- await ApplicationViewModel.InitDetex();
await _applicationView.CUE4Parse.Initialize();
await _applicationView.AesManager.InitAes();
await _applicationView.UpdateProvider(true);
@@ -74,6 +73,7 @@ public partial class MainWindow
_applicationView.CUE4Parse.VerifyConsoleVariables(),
_applicationView.CUE4Parse.VerifyOnDemandArchives(),
_applicationView.CUE4Parse.InitMappings(),
+ ApplicationViewModel.InitDetex(),
ApplicationViewModel.InitVgmStream(),
ApplicationViewModel.InitImGuiSettings(newOrUpdated),
Task.Run(() =>
@@ -86,7 +86,10 @@ public partial class MainWindow
#if DEBUG
// await _threadWorkerView.Begin(cancellationToken =>
// _applicationView.CUE4Parse.Extract(cancellationToken,
- // _applicationView.CUE4Parse.Provider["Marvel/Content/Marvel/Wwise/Assets/Events/Music/music_new/event/Entry.uasset"]));
+ // _applicationView.CUE4Parse.Provider["FortniteGame/Plugins/GameFeatures/BRCosmetics/Content/Characters/Player/Male/Medium/Bodies/M_MED_Lunch_Box/Meshes/M_MED_Lunch_Box.uasset"]));
+ await _threadWorkerView.Begin(cancellationToken =>
+ _applicationView.CUE4Parse.Extract(cancellationToken,
+ _applicationView.CUE4Parse.Provider["FortniteGame/Plugins/GameFeatures/BlastBerryMap/Content/Maps/BlastBerry_Terrain/_Generated_/D5V3O7ZS9ZLQKFKU6WFZ27OYW.umap"]));
#endif
}
diff --git a/FModel/ViewModels/ApiEndpoints/FModelApiEndpoint.cs b/FModel/ViewModels/ApiEndpoints/FModelApiEndpoint.cs
index aeaf42b9..47b7bd55 100644
--- a/FModel/ViewModels/ApiEndpoints/FModelApiEndpoint.cs
+++ b/FModel/ViewModels/ApiEndpoints/FModelApiEndpoint.cs
@@ -106,7 +106,11 @@ public class FModelApiEndpoint : AbstractApiProvider
public void CheckForUpdates(bool launch = false)
{
- if (DateTime.Now < UserSettings.Default.NextUpdateCheck) return;
+ if (DateTime.Now < UserSettings.Default.NextUpdateCheck)
+ {
+ Log.Warning("Updates have been silenced until {DateTime}", UserSettings.Default.NextUpdateCheck);
+ return;
+ }
if (launch)
{
@@ -140,7 +144,8 @@ public class FModelApiEndpoint : AbstractApiProvider
{
UserSettings.Default.LastUpdateCheck = DateTime.Now;
- if (((CustomMandatory)args.Mandatory).CommitHash == Constants.APP_COMMIT_ID)
+ var targetHash = ((CustomMandatory) args.Mandatory).CommitHash;
+ if (targetHash == Constants.APP_COMMIT_ID)
{
if (UserSettings.Default.ShowChangelog)
ShowChangelog(args);
@@ -152,6 +157,7 @@ public class FModelApiEndpoint : AbstractApiProvider
UserSettings.Default.ShowChangelog = currentVersion != args.InstalledVersion;
const string message = "A new update is available!";
+ Log.Warning("{message} Version {CurrentVersion} ({Hash})", message, currentVersion, targetHash);
Helper.OpenWindow(message, () => new UpdateView { Title = message, ResizeMode = ResizeMode.NoResize }.ShowDialog());
}
else
diff --git a/FModel/ViewModels/ApplicationViewModel.cs b/FModel/ViewModels/ApplicationViewModel.cs
index e3faa892..dd0806c2 100644
--- a/FModel/ViewModels/ApplicationViewModel.cs
+++ b/FModel/ViewModels/ApplicationViewModel.cs
@@ -260,7 +260,7 @@ public class ApplicationViewModel : ViewModel
ZlibHelper.Initialize(zlibPath);
}
- public static async ValueTask InitDetex()
+ public static async Task InitDetex()
{
var detexPath = Path.Combine(UserSettings.Default.OutputDirectory, ".data", DetexHelper.DLL_NAME);
if (File.Exists(DetexHelper.DLL_NAME))
diff --git a/FModel/Views/Snooper/Animations/TimeTracker.cs b/FModel/Views/Snooper/Animations/TimeTracker.cs
index 28cc9538..0fa761d3 100644
--- a/FModel/Views/Snooper/Animations/TimeTracker.cs
+++ b/FModel/Views/Snooper/Animations/TimeTracker.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Numerics;
using FModel.Views.Snooper.Shading;
+using FModel.Views.Snooper.Textures;
using ImGuiNET;
namespace FModel.Views.Snooper.Animations;
@@ -61,7 +62,7 @@ public class TimeTracker : IDisposable
}
private readonly string[] _icons = { "tl_forward", "tl_pause", "tl_rewind" };
- public void ImGuiTimeline(Snooper s, Save saver, Dictionary icons, List animations, Vector2 outliner, ImFontPtr fontPtr)
+ public void ImGuiTimeline(Snooper s, Save saver, Dictionary icons, List animations, Vector2 outliner, ImFontPtr fontPtr)
{
var dpiScale = ImGui.GetWindowDpiScale();
var thickness = 2.0f * dpiScale;
diff --git a/FModel/Views/Snooper/AssetPool.cs b/FModel/Views/Snooper/AssetPool.cs
new file mode 100644
index 00000000..123c06be
--- /dev/null
+++ b/FModel/Views/Snooper/AssetPool.cs
@@ -0,0 +1,266 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using CUE4Parse_Conversion.Meshes;
+using CUE4Parse_Conversion.Textures;
+using CUE4Parse.UE4.Assets.Exports;
+using CUE4Parse.UE4.Assets.Exports.Animation;
+using CUE4Parse.UE4.Assets.Exports.Component.SplineMesh;
+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.Misc;
+using CUE4Parse.UE4.Objects.UObject;
+using FModel.Settings;
+using FModel.Views.Snooper.Models;
+using FModel.Views.Snooper.Shading;
+using FModel.Views.Snooper.Textures;
+
+namespace FModel.Views.Snooper;
+
+public class AssetPool
+{
+ public static AssetPool Get() => _singleton ??= new AssetPool();
+ private static AssetPool _singleton;
+
+ public FGuid SelectedModel { get; private set; }
+
+ public readonly ConcurrentBag Queue;
+
+ public readonly Dictionary Models;
+ public readonly Dictionary Textures;
+
+ private readonly string _project;
+
+ private AssetPool()
+ {
+ Queue = [];
+
+ Models = [];
+ Textures = [];
+
+ _project = Services.ApplicationService.ApplicationView.CUE4Parse.Provider.ProjectName.ToUpper();
+ }
+
+ public void OnTick()
+ {
+ var current = 0;
+ while (Queue.TryTake(out var asset))
+ {
+ asset.Setup();
+ switch (asset)
+ {
+ case UModel uModel:
+ Models[asset.Guid] = uModel;
+ break;
+ case ITexture texture:
+ Textures[asset.Guid] = texture;
+ break;
+ }
+
+ current++;
+ if (current >= 1) break;
+ }
+
+ foreach (var model in Models.Values)
+ {
+ model.Update();
+ }
+ }
+
+ public void AddModel(UStaticMesh staticMesh)
+ {
+ ThreadPool.QueueUserWorkItem(_ =>
+ {
+ var guid = staticMesh.LightingGuid;
+ if (TryGetModel(guid, out var model))
+ {
+ model.AddInstance(Transform.Identity);
+ }
+ else if (staticMesh.TryConvert(out var mesh))
+ {
+ Queue.Add(new StaticModel(staticMesh, mesh) { Guid = guid });
+ }
+ });
+ }
+ public void AddModel(USkeletalMesh skeletalMesh)
+ {
+ ThreadPool.QueueUserWorkItem(_ =>
+ {
+ var guid = new FGuid((uint) skeletalMesh.GetFullName().GetHashCode());
+ if (!Models.ContainsKey(guid) && skeletalMesh.TryConvert(out var mesh))
+ {
+ Queue.Add(new SkeletalModel(skeletalMesh, mesh) { Guid = guid });
+ }
+ });
+ }
+ public void AddModel(USkeleton skeleton)
+ {
+ ThreadPool.QueueUserWorkItem(_ =>
+ {
+ var guid = skeleton.Guid;
+ if (!Models.ContainsKey(guid) && skeleton.TryConvert(out var _, out var box))
+ {
+ Queue.Add(new SkeletalModel(skeleton, box) { Guid = guid });
+ }
+ });
+ }
+ public void AddTexture(UTexture texture, bool fix)
+ {
+ ThreadPool.QueueUserWorkItem(_ =>
+ {
+ var guid = texture.LightingGuid;
+ if (TryGet(guid, out var _))
+ {
+ // do something here??
+ }
+ else if (texture.Format != EPixelFormat.PF_BC6H) // BC6H is not supported by Decode thus randomly crashes the app
+ {
+ var bitmap = texture switch
+ {
+ UTexture2D texture2D => texture2D.Decode(UserSettings.Default.PreviewMaxTextureSize, UserSettings.Default.CurrentDir.TexturePlatform),
+ UTexture2DArray texture2DArray => texture2DArray.DecodeTextureArray(UserSettings.Default.CurrentDir.TexturePlatform)?.FirstOrDefault(),
+ _ => texture.Decode(UserSettings.Default.CurrentDir.TexturePlatform)
+ };
+
+ if (bitmap is not null)
+ {
+ var t = new BitmapTexture(bitmap.ToSkBitmap(), texture);
+ if (fix) t.FixChannels(_project);
+
+ Queue.Add(t);
+ }
+ }
+ });
+ }
+
+ public void AddModel(IPropertyHolder actor, UObject staticMeshComponent, UStaticMesh staticMesh, Transform transform)
+ {
+ ThreadPool.QueueUserWorkItem(_ =>
+ {
+ var bSpline = staticMeshComponent is USplineMeshComponent;
+ var guid = staticMesh.LightingGuid;
+ if (TryGetModel(guid, out var model))
+ {
+ model.AddInstance(transform);
+ if (bSpline && model is SplineModel splineModel)
+ splineModel.AddComponent((USplineMeshComponent)staticMeshComponent);
+ }
+ else if (staticMesh.TryConvert(out var mesh))
+ {
+ model = bSpline ? new SplineModel(staticMesh, mesh, (USplineMeshComponent)staticMeshComponent, transform) : new StaticModel(staticMesh, mesh, transform);
+ model.Guid = guid;
+
+ if (actor.TryGetAllValues(out FPackageIndex[] textureData, "TextureData"))
+ {
+ var material = model.Materials.FirstOrDefault();
+ if (material is { IsUsed: true })
+ {
+ for (int j = 0; j < textureData.Length; j++)
+ {
+ if (textureData[j]?.Load() is not { } textureDataIdx)
+ continue;
+
+ if (textureDataIdx.TryGetValue(out FPackageIndex overrideMaterial, "OverrideMaterial") &&
+ overrideMaterial.TryLoad(out var oMaterial) && oMaterial is UMaterialInterface oUnrealMaterial)
+ material.SwapMaterial(oUnrealMaterial);
+
+ WorldTextureData(material, textureDataIdx, "Diffuse", j switch
+ {
+ 0 => "Diffuse",
+ > 0 => $"Diffuse_Texture_{j + 1}",
+ _ => CMaterialParams2.FallbackDiffuse
+ });
+ WorldTextureData(material, textureDataIdx, "Normal", j switch
+ {
+ 0 => "Normals",
+ > 0 => $"Normals_Texture_{j + 1}",
+ _ => CMaterialParams2.FallbackNormals
+ });
+ WorldTextureData(material, textureDataIdx, "Specular", j switch
+ {
+ 0 => "SpecularMasks",
+ > 0 => $"SpecularMasks_{j + 1}",
+ _ => CMaterialParams2.FallbackNormals
+ });
+ }
+ }
+ }
+
+ if (staticMeshComponent.TryGetValue(out FPackageIndex[] overrideMaterials, "OverrideMaterials"))
+ {
+ for (var j = 0; j < overrideMaterials.Length && j < model.Sections.Length; j++)
+ {
+ var matIndex = model.Sections[j].MaterialIndex;
+ if (matIndex < 0 || matIndex >= model.Materials.Length || matIndex >= overrideMaterials.Length ||
+ overrideMaterials[matIndex].Load() is not UMaterialInterface unrealMaterial) continue;
+
+ model.Materials[matIndex].SwapMaterial(unrealMaterial);
+ }
+ }
+
+ Queue.Add(model);
+ }
+ });
+
+ void WorldTextureData(Material material, UObject textureData, string name, string key)
+ {
+ if (textureData.TryGetValue(out FPackageIndex package, name) && package.Load() is UTexture2D texture)
+ material.Parameters.Textures[key] = texture;
+ }
+ }
+
+ public bool TryGet(FGuid guid, [MaybeNullWhen(true)] out T asset)
+ {
+ if (Models.TryGetValue(guid, out var m) && m is T model)
+ {
+ asset = model;
+ return true;
+ }
+
+ if (Textures.TryGetValue(guid, out var t) && t is T texture)
+ {
+ asset = texture;
+ return true;
+ }
+
+ foreach (var a in Queue)
+ {
+ if (a.Guid == guid && a is T value)
+ {
+ asset = value;
+ return true;
+ }
+ }
+
+ asset = default;
+ return false;
+ }
+ public bool TryGetModel([MaybeNullWhen(false)] out UModel model) => TryGet(SelectedModel, out model);
+ public bool TryGetModel(FGuid guid, [MaybeNullWhen(false)] out UModel model) => TryGet(guid, out model);
+ public bool TryGetTexture(FGuid guid, [MaybeNullWhen(false)] out ITexture texture) => TryGet(guid, out texture);
+
+ public void SelectModel(FGuid guid)
+ {
+ // unselect old
+ if (TryGetModel(out var model))
+ model.IsSelected = false;
+
+ // select new
+ if (!TryGetModel(guid, out model))
+ SelectedModel = Guid.Empty;
+ else
+ {
+ model.IsSelected = true;
+ SelectedModel = guid;
+ }
+
+ // SelectedSection = 0;
+ // SelectedMorph = 0;
+ }
+}
diff --git a/FModel/Views/Snooper/Buffers/FramebufferObject.cs b/FModel/Views/Snooper/Buffers/FramebufferObject.cs
index 2a0f2645..5c30d2a8 100644
--- a/FModel/Views/Snooper/Buffers/FramebufferObject.cs
+++ b/FModel/Views/Snooper/Buffers/FramebufferObject.cs
@@ -1,5 +1,6 @@
using System;
using FModel.Views.Snooper.Shading;
+using FModel.Views.Snooper.Textures;
using OpenTK.Graphics.OpenGL4;
namespace FModel.Views.Snooper.Buffers;
@@ -11,15 +12,15 @@ public class FramebufferObject : IDisposable
private int _width;
private int _height;
+ private readonly MsaaTexture _framebufferTexture;
private readonly RenderbufferObject _renderbuffer;
+ private readonly FramebufferTexture _postProcessingTexture;
private BufferObject _ebo;
private BufferObject _vbo;
private VertexArrayObject _vao;
private Shader _shader;
- private Texture _framebufferTexture;
- private Texture _postProcessingTexture;
public readonly uint[] Indices = { 0, 1, 2, 3, 4, 5 };
public readonly float[] Vertices = {
@@ -37,7 +38,9 @@ public class FramebufferObject : IDisposable
{
_width = size.X;
_height = size.Y;
+ _framebufferTexture = new MsaaTexture((uint) _width, (uint) _height);
_renderbuffer = new RenderbufferObject(_width, _height);
+ _postProcessingTexture = new FramebufferTexture(_width, _height);
}
public void Setup()
@@ -45,8 +48,7 @@ public class FramebufferObject : IDisposable
_framebufferHandle = GL.GenFramebuffer();
Bind();
- _framebufferTexture = new Texture((uint) _width, (uint) _height);
-
+ _framebufferTexture.Setup();
_renderbuffer.Setup();
_shader = new Shader("framebuffer");
@@ -69,7 +71,7 @@ public class FramebufferObject : IDisposable
_postProcessingHandle = GL.GenFramebuffer();
Bind(_postProcessingHandle);
- _postProcessingTexture = new Texture(_width, _height);
+ _postProcessingTexture.Setup();
status = GL.CheckFramebufferStatus(FramebufferTarget.Framebuffer);
if (status != FramebufferErrorCode.FramebufferComplete)
diff --git a/FModel/Views/Snooper/Buffers/PickingTexture.cs b/FModel/Views/Snooper/Buffers/PickingTexture.cs
index fe45cb7c..f4d27bf3 100644
--- a/FModel/Views/Snooper/Buffers/PickingTexture.cs
+++ b/FModel/Views/Snooper/Buffers/PickingTexture.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using CUE4Parse.UE4.Objects.Core.Misc;
using OpenTK.Graphics.OpenGL4;
using System.Numerics;
@@ -54,20 +55,18 @@ public class PickingTexture : IDisposable
Bind(0);
}
- public void Render(Matrix4x4 viewMatrix, Matrix4x4 projMatrix, IDictionary models)
+ public void Render(Matrix4x4 viewMatrix, Matrix4x4 projMatrix)
{
Bind();
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
_shader.Render(viewMatrix, projMatrix);
- foreach ((var guid, var model) in models)
+ foreach (var model in AssetPool.Get().Models.Values.Where(model => model.IsVisible))
{
- _shader.SetUniform("uA", guid.A);
- _shader.SetUniform("uB", guid.B);
- _shader.SetUniform("uC", guid.C);
- _shader.SetUniform("uD", guid.D);
-
- if (!model.IsVisible) continue;
+ _shader.SetUniform("uA", model.Guid.A);
+ _shader.SetUniform("uB", model.Guid.B);
+ _shader.SetUniform("uC", model.Guid.C);
+ _shader.SetUniform("uD", model.Guid.D);
model.PickingRender(_shader);
}
diff --git a/FModel/Views/Snooper/IDelayedSetup.cs b/FModel/Views/Snooper/IDelayedSetup.cs
new file mode 100644
index 00000000..3a963f83
--- /dev/null
+++ b/FModel/Views/Snooper/IDelayedSetup.cs
@@ -0,0 +1,11 @@
+using System;
+using CUE4Parse.UE4.Objects.Core.Misc;
+
+namespace FModel.Views.Snooper;
+
+public interface IDelayedSetup : IDisposable
+{
+ public FGuid Guid { get; }
+
+ public void Setup();
+}
diff --git a/FModel/Views/Snooper/Lights/Light.cs b/FModel/Views/Snooper/Lights/Light.cs
index 8159f3b5..8d8ce776 100644
--- a/FModel/Views/Snooper/Lights/Light.cs
+++ b/FModel/Views/Snooper/Lights/Light.cs
@@ -5,6 +5,7 @@ using CUE4Parse.UE4.Objects.Core.Math;
using CUE4Parse.UE4.Objects.Core.Misc;
using FModel.Views.Snooper.Buffers;
using FModel.Views.Snooper.Shading;
+using FModel.Views.Snooper.Textures;
using ImGuiNET;
using OpenTK.Graphics.OpenGL4;
@@ -29,14 +30,14 @@ public abstract class Light : IDisposable
1f, -1f, 0
};
public readonly FGuid Model;
- public readonly Texture Icon;
+ public readonly ITexture Icon;
public Transform Transform;
public Vector4 Color;
public float Intensity;
public bool IsSetup;
- public Light(Texture icon, UObject light)
+ public Light(ITexture icon, UObject light)
{
Transform = new Transform
{
@@ -52,7 +53,7 @@ public abstract class Light : IDisposable
Intensity = light.GetOrDefault("Intensity", 1.0f);
}
- public Light(FGuid model, Texture icon, UObject parent, UObject light, Transform transform)
+ public Light(FGuid model, ITexture icon, UObject parent, UObject light, Transform transform)
{
Transform = new Transform
{
diff --git a/FModel/Views/Snooper/Lights/PointLight.cs b/FModel/Views/Snooper/Lights/PointLight.cs
index 55fcda20..c6bd0a70 100644
--- a/FModel/Views/Snooper/Lights/PointLight.cs
+++ b/FModel/Views/Snooper/Lights/PointLight.cs
@@ -2,6 +2,7 @@
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Objects.Core.Misc;
using FModel.Views.Snooper.Shading;
+using FModel.Views.Snooper.Textures;
using ImGuiNET;
namespace FModel.Views.Snooper.Lights;
@@ -11,7 +12,7 @@ public class PointLight : Light
public float Linear;
public float Quadratic;
- public PointLight(Texture icon, UObject point) : base(icon, point)
+ public PointLight(ITexture icon, UObject point) : base(icon, point)
{
if (!point.TryGetValue(out float radius, "SourceRadius", "AttenuationRadius"))
radius = 1.0f;
@@ -21,7 +22,7 @@ public class PointLight : Light
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)
+ public PointLight(FGuid model, ITexture icon, UObject parent, UObject point, Transform transform) : base(model, icon, parent, point, transform)
{
if (!point.TryGetValue(out float radius, "AttenuationRadius", "SourceRadius"))
radius = 1.0f;
diff --git a/FModel/Views/Snooper/Lights/SpotLight.cs b/FModel/Views/Snooper/Lights/SpotLight.cs
index 3800d888..d8245234 100644
--- a/FModel/Views/Snooper/Lights/SpotLight.cs
+++ b/FModel/Views/Snooper/Lights/SpotLight.cs
@@ -1,6 +1,7 @@
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Objects.Core.Misc;
using FModel.Views.Snooper.Shading;
+using FModel.Views.Snooper.Textures;
using ImGuiNET;
namespace FModel.Views.Snooper.Lights;
@@ -11,7 +12,7 @@ public class SpotLight : Light
public float InnerConeAngle;
public float OuterConeAngle;
- public SpotLight(Texture icon, UObject spot) : base(icon, spot)
+ public SpotLight(ITexture icon, UObject spot) : base(icon, spot)
{
if (!spot.TryGetValue(out Attenuation, "SourceRadius", "AttenuationRadius"))
Attenuation = 1.0f;
@@ -23,7 +24,7 @@ public class SpotLight : Light
InnerConeAngle = OuterConeAngle - 10;
}
- public SpotLight(FGuid model, Texture icon, UObject parent, UObject spot, Transform transform) : base(model, icon, parent, spot, transform)
+ public SpotLight(FGuid model, ITexture icon, UObject parent, UObject spot, Transform transform) : base(model, icon, parent, spot, transform)
{
if (!spot.TryGetValue(out Attenuation, "AttenuationRadius", "SourceRadius"))
Attenuation = 1.0f;
diff --git a/FModel/Views/Snooper/Models/IRenderableModel.cs b/FModel/Views/Snooper/Models/IRenderableModel.cs
index a863ec98..8f8b7e8f 100644
--- a/FModel/Views/Snooper/Models/IRenderableModel.cs
+++ b/FModel/Views/Snooper/Models/IRenderableModel.cs
@@ -1,12 +1,12 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Numerics;
using FModel.Views.Snooper.Buffers;
using FModel.Views.Snooper.Shading;
+using FModel.Views.Snooper.Textures;
namespace FModel.Views.Snooper.Models;
-public interface IRenderableModel : IDisposable
+public interface IRenderableModel : IDelayedSetup
{
protected int Handle { get; set; }
protected BufferObject Ebo { get; set; }
@@ -29,10 +29,7 @@ public interface IRenderableModel : IDisposable
public bool IsSelected { get; set; }
public bool ShowWireframe { get; set; }
- public void Setup(Options options);
- public void SetupInstances();
- public void Render(Shader shader, Texture checker = null, bool outline = false);
+ public void Render(Shader shader, ITexture checker = null, bool outline = false);
public void PickingRender(Shader shader);
- public void Update(Options options);
public void AddInstance(Transform transform);
}
diff --git a/FModel/Views/Snooper/Models/Section.cs b/FModel/Views/Snooper/Models/Section.cs
index 6720e167..57e1f70d 100644
--- a/FModel/Views/Snooper/Models/Section.cs
+++ b/FModel/Views/Snooper/Models/Section.cs
@@ -23,9 +23,11 @@ public class Section
Color = Constants.COLOR_PALETTE[MaterialIndex % Constants.PALETTE_LENGTH];
}
- public void SetupMaterial(Material material)
+ public void ValidateMaterial(Material material, int uvCount)
{
material.IsUsed = true;
Show = !material.Parameters.IsNull && !material.Parameters.IsTranslucent;
+
+ material.Validate(uvCount);
}
}
diff --git a/FModel/Views/Snooper/Models/SkeletalModel.cs b/FModel/Views/Snooper/Models/SkeletalModel.cs
index fcc25ac8..752db222 100644
--- a/FModel/Views/Snooper/Models/SkeletalModel.cs
+++ b/FModel/Views/Snooper/Models/SkeletalModel.cs
@@ -131,9 +131,9 @@ public class SkeletalModel : UModel
IsVisible = true;
}
- public override void Setup(Options options)
+ public override void Setup()
{
- base.Setup(options);
+ base.Setup();
Skeleton.Setup();
if (!HasMorphTargets) return;
diff --git a/FModel/Views/Snooper/Models/Skybox.cs b/FModel/Views/Snooper/Models/Skybox.cs
index af04e3b5..e2efd744 100644
--- a/FModel/Views/Snooper/Models/Skybox.cs
+++ b/FModel/Views/Snooper/Models/Skybox.cs
@@ -2,6 +2,7 @@
using System.Numerics;
using FModel.Views.Snooper.Buffers;
using FModel.Views.Snooper.Shading;
+using FModel.Views.Snooper.Textures;
using OpenTK.Graphics.OpenGL4;
namespace FModel.Views.Snooper.Models;
@@ -16,7 +17,7 @@ public class Skybox : IDisposable
private string[] _textures = { "px", "nx", "py", "ny", "pz", "nz" };
- private Texture _cubeMap;
+ private ITexture _cubeMap;
private Shader _shader;
public readonly uint[] Indices = { 0, 1, 3, 1, 2, 3 };
@@ -75,7 +76,7 @@ public class Skybox : IDisposable
_vbo = new BufferObject(Vertices, BufferTarget.ArrayBuffer);
_vao = new VertexArrayObject(_vbo, _ebo);
- _cubeMap = new Texture(_textures);
+ _cubeMap = new ApplicationTexture(_textures);
_shader = new Shader("skybox");
_vao.VertexAttributePointer(0, 3, VertexAttribPointerType.Float, 3, 0); // position
diff --git a/FModel/Views/Snooper/Models/SplineModel.cs b/FModel/Views/Snooper/Models/SplineModel.cs
index 3850a4ce..1cf13b91 100644
--- a/FModel/Views/Snooper/Models/SplineModel.cs
+++ b/FModel/Views/Snooper/Models/SplineModel.cs
@@ -88,9 +88,9 @@ public class SplineModel : StaticModel
_splineParams.Add(new GpuParams(splineMesh));
}
- public override void Setup(Options options)
+ public override void Setup()
{
- base.Setup(options);
+ base.Setup();
_ssbo = new BufferObject(_splineParams.ToArray(), BufferTarget.ShaderStorageBuffer);
}
diff --git a/FModel/Views/Snooper/Models/UModel.cs b/FModel/Views/Snooper/Models/UModel.cs
index da3be5b4..530f9917 100644
--- a/FModel/Views/Snooper/Models/UModel.cs
+++ b/FModel/Views/Snooper/Models/UModel.cs
@@ -9,10 +9,12 @@ using CUE4Parse.UE4.Assets;
using CUE4Parse.UE4.Assets.Exports;
using CUE4Parse.UE4.Assets.Exports.Material;
using CUE4Parse.UE4.Objects.Core.Math;
+using CUE4Parse.UE4.Objects.Core.Misc;
using CUE4Parse.Utils;
using FModel.Settings;
using FModel.Views.Snooper.Buffers;
using FModel.Views.Snooper.Shading;
+using FModel.Views.Snooper.Textures;
using OpenTK.Graphics.OpenGL4;
namespace FModel.Views.Snooper.Models;
@@ -31,15 +33,15 @@ public abstract class UModel : IRenderableModel
private readonly UObject _export;
private readonly List _vertexAttributes =
[
- new VertexAttribute { Size = 1, Type = VertexAttribPointerType.Int, Enabled = false }, // VertexIndex
- new VertexAttribute { Size = 3, Type = VertexAttribPointerType.Float, Enabled = true }, // Position
- new VertexAttribute { Size = 3, Type = VertexAttribPointerType.Float, Enabled = false }, // Normal
- new VertexAttribute { Size = 3, Type = VertexAttribPointerType.Float, Enabled = false }, // Tangent
- new VertexAttribute { Size = 2, Type = VertexAttribPointerType.Float, Enabled = false }, // UV
- new VertexAttribute { Size = 1, Type = VertexAttribPointerType.Float, Enabled = false }, // TextureLayer
- new VertexAttribute { Size = 1, Type = VertexAttribPointerType.Float, Enabled = false }, // Colors
- new VertexAttribute { Size = 4, Type = VertexAttribPointerType.Float, Enabled = false }, // BoneIds
- new VertexAttribute { Size = 4, Type = VertexAttribPointerType.Float, Enabled = false } // BoneWeights
+ new() { Size = 1, Type = VertexAttribPointerType.Int, Enabled = false }, // VertexIndex
+ new() { Size = 3, Type = VertexAttribPointerType.Float, Enabled = true }, // Position
+ new() { Size = 3, Type = VertexAttribPointerType.Float, Enabled = false }, // Normal
+ new() { Size = 3, Type = VertexAttribPointerType.Float, Enabled = false }, // Tangent
+ new() { Size = 2, Type = VertexAttribPointerType.Float, Enabled = false }, // UV
+ new() { Size = 1, Type = VertexAttribPointerType.Float, Enabled = false }, // TextureLayer
+ new() { Size = 1, Type = VertexAttribPointerType.Float, Enabled = false }, // Colors
+ new() { Size = 4, Type = VertexAttribPointerType.Float, Enabled = false }, // BoneIds
+ new() { Size = 4, Type = VertexAttribPointerType.Float, Enabled = false } // BoneWeights
];
public int Handle { get; set; }
@@ -48,6 +50,7 @@ public abstract class UModel : IRenderableModel
public BufferObject MatrixVbo { get; set; }
public VertexArrayObject Vao { get; set; }
+ public FGuid Guid { get; set; }
public string Path { get; }
public string Name { get; }
public string Type { get; protected set; }
@@ -177,13 +180,13 @@ public abstract class UModel : IRenderableModel
{
var section = lod.Sections.Value[s];
Sections[s] = new Section(section.MaterialIndex, section.NumFaces * 3, section.FirstIndex);
- if (section.IsValid) Sections[s].SetupMaterial(Materials[section.MaterialIndex]);
+ if (section.IsValid) Sections[s].ValidateMaterial(Materials[section.MaterialIndex], UvCount);
}
AddInstance(transform ?? Transform.Identity);
}
- public virtual void Setup(Options options)
+ public virtual void Setup()
{
Handle = GL.CreateProgram();
Ebo = new BufferObject(Indices, BufferTarget.ElementArrayBuffer);
@@ -211,7 +214,7 @@ public abstract class UModel : IRenderableModel
for (var i = 0; i < Materials.Length; i++)
{
if (!Materials[i].IsUsed) continue;
- Materials[i].Setup(options, broken ? 1 : UvCount);
+ Materials[i].Setup(broken);
}
foreach (var collision in Collisions)
@@ -219,7 +222,7 @@ public abstract class UModel : IRenderableModel
collision.Setup();
}
- if (options.Models.Count == 1 && Sections.All(x => !x.Show)) // visible if alone and invisible
+ // if (options.Models.Count == 1 && Sections.All(x => !x.Show)) // visible if alone and invisible
{
IsVisible = true;
foreach (var section in Sections)
@@ -227,26 +230,26 @@ public abstract class UModel : IRenderableModel
section.Show = true;
}
}
- else if (!IsVisible) // default: visible if one section is visible
- {
- foreach (var section in Sections)
- {
- if (section.Show)
- {
- IsVisible = true;
- break;
- }
- }
- }
- else foreach (var section in Sections) // force visibility
- {
- section.Show = true;
- }
+ // else if (!IsVisible) // default: visible if one section is visible
+ // {
+ // foreach (var section in Sections)
+ // {
+ // if (section.Show)
+ // {
+ // IsVisible = true;
+ // break;
+ // }
+ // }
+ // }
+ // else foreach (var section in Sections) // force visibility
+ // {
+ // section.Show = true;
+ // }
IsSetup = true;
}
- public virtual void Render(Shader shader, Texture checker = null, bool outline = false)
+ public virtual void Render(Shader shader, ITexture checker = null, bool outline = false)
{
if (outline) GL.Disable(EnableCap.DepthTest);
if (IsTwoSided) GL.Disable(EnableCap.CullFace);
@@ -332,7 +335,7 @@ public abstract class UModel : IRenderableModel
shader.SetUniform("uScaleDown", Constants.SCALE_DOWN_RATIO);
}
- public void Update(Options options)
+ public void Update()
{
MatrixVbo.Bind();
for (int instance = 0; instance < TransformsCount; instance++)
@@ -353,11 +356,11 @@ public abstract class UModel : IRenderableModel
var socketRelation = boneMatrix * worldMatrix;
foreach (var info in socket.AttachedModels)
{
- if (!options.TryGetModel(info.Guid, out var attachedModel))
+ if (!AssetPool.Get().TryGetModel(info.Guid, out var attachedModel))
continue;
attachedModel.Transforms[info.Instance].Relation = socket.Transform.LocalMatrix * socketRelation;
- attachedModel.Update(options);
+ attachedModel.Update();
}
}
}
diff --git a/FModel/Views/Snooper/Options.cs b/FModel/Views/Snooper/Options.cs
index 0f55dd27..346f0e01 100644
--- a/FModel/Views/Snooper/Options.cs
+++ b/FModel/Views/Snooper/Options.cs
@@ -9,6 +9,7 @@ using FModel.Views.Snooper.Animations;
using FModel.Views.Snooper.Lights;
using FModel.Views.Snooper.Models;
using FModel.Views.Snooper.Shading;
+using FModel.Views.Snooper.Textures;
using SkiaSharp;
namespace FModel.Views.Snooper;
@@ -22,49 +23,54 @@ public class Options
public int SelectedAnimation{ get; private set; }
public readonly Dictionary Models;
- public readonly Dictionary Textures;
+ public readonly Dictionary Textures;
public readonly List Lights;
public readonly TimeTracker Tracker;
public readonly List Animations;
- public readonly Dictionary Icons;
+ public readonly Dictionary Icons;
private readonly string _game;
public Options()
{
Models = new Dictionary();
- Textures = new Dictionary();
+ Textures = new Dictionary();
Lights = new List();
Tracker = new TimeTracker();
Animations = new List();
- Icons = new Dictionary
+ Icons = new Dictionary
{
- ["material"] = new ("materialicon"),
- ["square"] = new ("square"),
- ["square_off"] = new ("square_off"),
- ["cube"] = new ("cube"),
- ["cube_off"] = new ("cube_off"),
- ["light"] = new ("light"),
- ["light_off"] = new ("light_off"),
- ["noimage"] = new ("T_Placeholder_Item_Image"),
- ["checker"] = new ("checker"),
- ["pointlight"] = new ("pointlight"),
- ["spotlight"] = new ("spotlight"),
- ["link_on"] = new ("link_on"),
- ["link_off"] = new ("link_off"),
- ["link_has"] = new ("link_has"),
- ["tl_play"] = new ("tl_play"),
- ["tl_pause"] = new ("tl_pause"),
- ["tl_rewind"] = new ("tl_rewind"),
- ["tl_forward"] = new ("tl_forward"),
- ["tl_previous"] = new ("tl_previous"),
- ["tl_next"] = new ("tl_next"),
+ ["material"] = new ApplicationTexture("materialicon"),
+ ["square"] = new ApplicationTexture("square"),
+ ["square_off"] = new ApplicationTexture("square_off"),
+ ["cube"] = new ApplicationTexture("cube"),
+ ["cube_off"] = new ApplicationTexture("cube_off"),
+ ["light"] = new ApplicationTexture("light"),
+ ["light_off"] = new ApplicationTexture("light_off"),
+ ["noimage"] = new ApplicationTexture("T_Placeholder_Item_Image"),
+ ["checker"] = new ApplicationTexture("checker"),
+ ["pointlight"] = new ApplicationTexture("pointlight"),
+ ["spotlight"] = new ApplicationTexture("spotlight"),
+ ["link_on"] = new ApplicationTexture("link_on"),
+ ["link_off"] = new ApplicationTexture("link_off"),
+ ["link_has"] = new ApplicationTexture("link_has"),
+ ["tl_play"] = new ApplicationTexture("tl_play"),
+ ["tl_pause"] = new ApplicationTexture("tl_pause"),
+ ["tl_rewind"] = new ApplicationTexture("tl_rewind"),
+ ["tl_forward"] = new ApplicationTexture("tl_forward"),
+ ["tl_previous"] = new ApplicationTexture("tl_previous"),
+ ["tl_next"] = new ApplicationTexture("tl_next"),
};
+ foreach (var icon in Icons.Values)
+ {
+ icon.Setup();
+ }
+
_game = Services.ApplicationService.ApplicationView.CUE4Parse.Provider.ProjectName.ToUpper();
SelectModel(Guid.Empty);
@@ -72,11 +78,11 @@ public class Options
public void SetupModelsAndLights()
{
- foreach (var model in Models.Values)
- {
- if (model.IsSetup) continue;
- model.Setup(this);
- }
+ // foreach (var model in Models.Values)
+ // {
+ // if (model.IsSetup) continue;
+ // model.Setup(this);
+ // }
foreach (var light in Lights)
{
@@ -187,29 +193,6 @@ public class Options
model.UpdateMorph(SelectedMorph);
}
- public bool TryGetTexture(UTexture o, bool fix, out Texture texture)
- {
- var guid = o.LightingGuid;
- if (Textures.TryGetValue(guid, out texture)) return texture != null;
- if (o.Format == EPixelFormat.PF_BC6H) return false; // BC6H is not supported by Decode thus randomly crashes the app
-
- var bitmap = o switch
- {
- UTexture2D texture2D => texture2D.Decode(UserSettings.Default.PreviewMaxTextureSize, UserSettings.Default.CurrentDir.TexturePlatform),
- UTexture2DArray texture2DArray => texture2DArray.DecodeTextureArray(UserSettings.Default.CurrentDir.TexturePlatform)?.FirstOrDefault(),
- _ => o.Decode(UserSettings.Default.CurrentDir.TexturePlatform)
- };
-
- if (bitmap is not null)
- {
- texture = new Texture(bitmap.ToSkBitmap(), o);
- if (fix) TextureHelper.FixChannels(_game, texture);
- Textures[guid] = texture;
- }
-
- return texture != null;
- }
-
public bool TryGetModel(out UModel model) => Models.TryGetValue(SelectedModel, out model);
public bool TryGetModel(FGuid guid, out UModel model) => Models.TryGetValue(guid, out model);
diff --git a/FModel/Views/Snooper/Renderer.cs b/FModel/Views/Snooper/Renderer.cs
index dfa91ad3..725e88dd 100644
--- a/FModel/Views/Snooper/Renderer.cs
+++ b/FModel/Views/Snooper/Renderer.cs
@@ -86,13 +86,13 @@ public class Renderer : IDisposable
switch (dummy)
{
case UStaticMesh when export.Value is UStaticMesh st:
- LoadStaticMesh(st);
+ AssetPool.Get().AddModel(st);
break;
case USkeletalMesh when export.Value is USkeletalMesh sk:
- LoadSkeletalMesh(sk);
+ AssetPool.Get().AddModel(sk);
break;
case USkeleton when export.Value is USkeleton skel:
- LoadSkeleton(skel);
+ AssetPool.Get().AddModel(skel);
break;
case UMaterialInstance when export.Value is UMaterialInstance mi:
LoadMaterialInstance(mi);
@@ -117,7 +117,7 @@ public class Renderer : IDisposable
if (!Options.TryGetModel(out var model) || !Options.TryGetSection(model, out var section)) return;
model.Materials[section.MaterialIndex].SwapMaterial(unrealMaterial);
- Application.Current.Dispatcher.Invoke(() => model.Materials[section.MaterialIndex].Setup(Options, model.UvCount));
+ // Application.Current.Dispatcher.Invoke(() => model.Materials[section.MaterialIndex].Setup(model.UvCount));
}
public void Animate(UObject anim)
@@ -242,7 +242,7 @@ public class Renderer : IDisposable
_collision = new Shader("collision", "bone");
Picking.Setup();
- Options.SetupModelsAndLights();
+ // Options.SetupModelsAndLights();
}
public void Render()
@@ -258,9 +258,8 @@ public class Renderer : IDisposable
_shader.SetUniform($"bVertexColors[{i}]", i == (int) Color);
// render model pass
- foreach (var model in Options.Models.Values)
+ foreach (var model in AssetPool.Get().Models.Values.Where(model => model.IsVisible))
{
- if (!model.IsVisible) continue;
model.Render(_shader, Color == VertexColor.TextureCoordinates ? Options.Icons["checker"] : null);
}
@@ -278,7 +277,7 @@ public class Renderer : IDisposable
}
// debug + outline pass
- if (Options.TryGetModel(out var selected) && selected.IsVisible)
+ if (AssetPool.Get().TryGetModel(out var selected) && selected.IsVisible)
{
if (IsSkeletonTreeOpen && selected is SkeletalModel skeletalModel)
{
@@ -296,7 +295,7 @@ public class Renderer : IDisposable
}
// picking pass (dedicated FBO, binding to 0 afterward)
- Picking.Render(viewMatrix, projMatrix, Options.Models);
+ Picking.Render(viewMatrix, projMatrix);
}
public void Update(Snooper wnd, float deltaSeconds)
@@ -313,14 +312,15 @@ public class Renderer : IDisposable
}
{
- foreach (var model in Options.Models.Values)
- {
- model.Update(Options);
- }
- if (IsSkeletonTreeOpen && Options.TryGetModel(out var selected) && selected is SkeletalModel { IsVisible: true } skeletalModel)
- {
- skeletalModel.Skeleton.UpdateVertices();
- }
+ AssetPool.Get().OnTick();
+ // foreach (var model in Options.Models.Values)
+ // {
+ // model.Update(Options);
+ // }
+ // if (IsSkeletonTreeOpen && Options.TryGetModel(out var selected) && selected is SkeletalModel { IsVisible: true } skeletalModel)
+ // {
+ // skeletalModel.Skeleton.UpdateVertices();
+ // }
}
CameraOp.Modify(wnd.KeyboardState, deltaSeconds);
@@ -343,33 +343,6 @@ public class Renderer : IDisposable
wnd.WindowShouldClose(true, true);
}
- 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;
- }
-
- if (!original.TryConvert(out var mesh))
- return;
-
- Options.Models[guid] = new StaticModel(original, mesh);
- Options.SelectModel(guid);
- }
-
- private void LoadSkeletalMesh(USkeletalMesh original)
- {
- var guid = new FGuid((uint) original.GetFullName().GetHashCode());
- if (Options.Models.ContainsKey(guid) || !original.TryConvert(out var mesh)) return;
-
- var skeletalModel = new SkeletalModel(original, mesh);
- Options.Models[guid] = skeletalModel;
- Options.SelectModel(guid);
- }
-
private void LoadSkeleton(USkeleton original)
{
var guid = original.Guid;
@@ -390,7 +363,7 @@ public class Renderer : IDisposable
if (Options.TryGetModel(guid, out var model))
{
model.Materials[0].SwapMaterial(original);
- Application.Current.Dispatcher.Invoke(() => model.Materials[0].Setup(Options, model.UvCount));
+ // Application.Current.Dispatcher.Invoke(() => model.Materials[0].Setup(Options, model.UvCount));
return;
}
@@ -410,7 +383,7 @@ public class Renderer : IDisposable
if (Options.TryGetModel(guid, out var model))
{
model.AddInstance(Transform.Identity);
- Application.Current.Dispatcher.Invoke(() => model.SetupInstances());
+ // Application.Current.Dispatcher.Invoke(() => model.SetupInstances());
return;
}
@@ -589,87 +562,18 @@ public class Renderer : IDisposable
}
private void ProcessMesh(IPropertyHolder actor, UObject staticMeshComp, UStaticMesh m, Transform transform, bool forceShow)
{
- var bSpline = staticMeshComp is USplineMeshComponent;
- var guid = m.LightingGuid;
- if (Options.TryGetModel(guid, out var model))
- {
- model.AddInstance(transform);
- if (bSpline && model is SplineModel splineModel)
- splineModel.AddComponent((USplineMeshComponent)staticMeshComp);
- }
- else if (m.TryConvert(out var mesh))
- {
- model = bSpline ? new SplineModel(m, mesh, (USplineMeshComponent)staticMeshComp, transform) : new StaticModel(m, mesh, transform);
- model.IsTwoSided = actor.GetOrDefault("bMirrored", staticMeshComp.GetOrDefault("bDisallowMeshPaintPerInstance", model.IsTwoSided));
+ AssetPool.Get().AddModel(actor, staticMeshComp, m, transform);
- if (actor.TryGetAllValues(out FPackageIndex[] textureData, "TextureData"))
- {
- var material = model.Materials.FirstOrDefault();
- if (material is { IsUsed: true })
- {
- for (int j = 0; j < textureData.Length; j++)
- {
- if (textureData[j]?.Load() is not { } textureDataIdx)
- continue;
-
- if (textureDataIdx.TryGetValue(out FPackageIndex overrideMaterial, "OverrideMaterial") &&
- overrideMaterial.TryLoad(out var oMaterial) && oMaterial is UMaterialInterface oUnrealMaterial)
- material.SwapMaterial(oUnrealMaterial);
-
- WorldTextureData(material, textureDataIdx, "Diffuse", j switch
- {
- 0 => "Diffuse",
- > 0 => $"Diffuse_Texture_{j + 1}",
- _ => CMaterialParams2.FallbackDiffuse
- });
- WorldTextureData(material, textureDataIdx, "Normal", j switch
- {
- 0 => "Normals",
- > 0 => $"Normals_Texture_{j + 1}",
- _ => CMaterialParams2.FallbackNormals
- });
- WorldTextureData(material, textureDataIdx, "Specular", j switch
- {
- 0 => "SpecularMasks",
- > 0 => $"SpecularMasks_{j + 1}",
- _ => CMaterialParams2.FallbackNormals
- });
- }
- }
- }
-
- if (staticMeshComp.TryGetValue(out FPackageIndex[] overrideMaterials, "OverrideMaterials"))
- {
- for (var j = 0; j < overrideMaterials.Length && j < model.Sections.Length; j++)
- {
- var matIndex = model.Sections[j].MaterialIndex;
- if (matIndex < 0 || matIndex >= model.Materials.Length || matIndex >= overrideMaterials.Length ||
- overrideMaterials[matIndex].Load() is not UMaterialInterface unrealMaterial) continue;
-
- model.Materials[matIndex].SwapMaterial(unrealMaterial);
- }
- }
-
- if (forceShow)
- {
- foreach (var section in model.Sections)
- {
- section.Show = true;
- }
- }
- Options.Models[guid] = model;
- }
-
- if (actor.TryGetValue(out FPackageIndex treasureLight, "PointLight", "TreasureLight") &&
- treasureLight.TryLoad(out var pl1) && pl1.Template.TryLoad(out var pl2))
- {
- Options.Lights.Add(new PointLight(guid, Options.Icons["pointlight"], pl1, pl2, transform));
- }
- if (actor.TryGetValue(out FPackageIndex spotLight, "SpotLight") &&
- spotLight.TryLoad(out var sl1) && sl1.Template.TryLoad(out var sl2))
- {
- Options.Lights.Add(new SpotLight(guid, Options.Icons["spotlight"], sl1, sl2, transform));
- }
+ // if (actor.TryGetValue(out FPackageIndex treasureLight, "PointLight", "TreasureLight") &&
+ // treasureLight.TryLoad(out var pl1) && pl1.Template.TryLoad(out var pl2))
+ // {
+ // Options.Lights.Add(new PointLight(guid, Options.Icons["pointlight"], pl1, pl2, transform));
+ // }
+ // if (actor.TryGetValue(out FPackageIndex spotLight, "SpotLight") &&
+ // spotLight.TryLoad(out var sl1) && sl1.Template.TryLoad(out var sl2))
+ // {
+ // Options.Lights.Add(new SpotLight(guid, Options.Icons["spotlight"], sl1, sl2, transform));
+ // }
}
private Transform CalculateTransform(IPropertyHolder staticMeshComp, Transform relation)
diff --git a/FModel/Views/Snooper/Shading/Material.cs b/FModel/Views/Snooper/Shading/Material.cs
index 729aa766..83765af3 100644
--- a/FModel/Views/Snooper/Shading/Material.cs
+++ b/FModel/Views/Snooper/Shading/Material.cs
@@ -9,6 +9,7 @@ using CUE4Parse.UE4.Objects.Core.Misc;
using FModel.Extensions;
using FModel.Settings;
using FModel.Views.Snooper.Models;
+using FModel.Views.Snooper.Textures;
using ImGuiNET;
using OpenTK.Graphics.OpenGL4;
@@ -25,10 +26,10 @@ public class Material : IDisposable
public int SelectedTexture;
public bool IsUsed;
- public Texture[] Diffuse;
- public Texture[] Normals;
- public Texture[] SpecularMasks;
- public Texture[] Emissive;
+ public FGuid[] Diffuse;
+ public FGuid[] Normals;
+ public FGuid[] SpecularMasks;
+ public FGuid[] Emissive;
public Vector4[] DiffuseColor;
public Vector4[] EmissiveColor;
@@ -48,13 +49,13 @@ public class Material : IDisposable
Path = "None";
IsUsed = false;
- Diffuse = Array.Empty();
- Normals = Array.Empty();
- SpecularMasks = Array.Empty();
- Emissive = Array.Empty();
+ Diffuse = [];
+ Normals = [];
+ SpecularMasks = [];
+ Emissive = [];
- DiffuseColor = Array.Empty();
- EmissiveColor = Array.Empty();
+ DiffuseColor = [];
+ EmissiveColor = [];
EmissiveRegion = new Vector4(0, 0, 1, 1);
}
@@ -70,39 +71,36 @@ public class Material : IDisposable
unrealMaterial.GetParams(Parameters, UserSettings.Default.MaterialExportFormat);
}
- public void Setup(Options options, int uvCount)
+ public void Validate(int uvCount)
{
- _handle = GL.CreateProgram();
-
if (uvCount < 1 || Parameters.IsNull)
{
- Diffuse = [new Texture(FLinearColor.Gray)];
- Normals = [new Texture(new FLinearColor(0.5f, 0.5f, 1f, 1f))];
- SpecularMasks = [new Texture(new FLinearColor(1f, 0.5f, 0.5f, 1f))];
- Emissive = new Texture[1];
- DiffuseColor = FillColors(1, Diffuse, CMaterialParams2.DiffuseColors, Vector4.One);
+ Diffuse = [new LinearColorTexture(FLinearColor.Gray).Guid];
+ Normals = [new LinearColorTexture(new FLinearColor(0.5f, 0.5f, 1f, 1f)).Guid];
+ SpecularMasks = [];
+ Emissive = new FGuid[1];
+ DiffuseColor = FillColors(1, CMaterialParams2.DiffuseColors, Vector4.One);
EmissiveColor = [Vector4.One];
}
else
{
- { // textures
- Diffuse = FillTextures(options, uvCount, Parameters.HasTopDiffuse, CMaterialParams2.Diffuse, CMaterialParams2.FallbackDiffuse, true);
- Normals = FillTextures(options, uvCount, Parameters.HasTopNormals, CMaterialParams2.Normals, CMaterialParams2.FallbackNormals);
- SpecularMasks = FillTextures(options, uvCount, Parameters.HasTopSpecularMasks, CMaterialParams2.SpecularMasks, CMaterialParams2.FallbackSpecularMasks);
- Emissive = FillTextures(options, uvCount, true, CMaterialParams2.Emissive, CMaterialParams2.FallbackEmissive);
- }
+ // textures
+ Diffuse = FillTextures(uvCount, Parameters.HasTopDiffuse, CMaterialParams2.Diffuse, CMaterialParams2.FallbackDiffuse, true);
+ Normals = FillTextures(uvCount, Parameters.HasTopNormals, CMaterialParams2.Normals, CMaterialParams2.FallbackNormals);
+ SpecularMasks = FillTextures(uvCount, Parameters.HasTopSpecularMasks, CMaterialParams2.SpecularMasks, CMaterialParams2.FallbackSpecularMasks);
+ Emissive = FillTextures(uvCount, true, CMaterialParams2.Emissive, CMaterialParams2.FallbackEmissive);
- { // colors
- DiffuseColor = FillColors(uvCount, Diffuse, CMaterialParams2.DiffuseColors, Vector4.One);
- EmissiveColor = FillColors(uvCount, Emissive, CMaterialParams2.EmissiveColors, Vector4.One);
- }
+ // colors
+ DiffuseColor = FillColors(uvCount, CMaterialParams2.DiffuseColors, Vector4.One);
+ EmissiveColor = FillColors(uvCount, CMaterialParams2.EmissiveColors, Vector4.One);
{ // ambient occlusion + color boost
- if (Parameters.TryGetTexture2d(out var original, "M", "AEM", "AO") &&
- !original.Name.Equals("T_BlackMask") && options.TryGetTexture(original, false, out var transformed))
+ if (Parameters.TryGetTexture2d(out var original, "M", "AEM", "AO") && !original.Name.Equals("T_BlackMask"))
{
+ AssetPool.Get().AddTexture(original, false);
+
HasAo = true;
- Ao = new AoParams { Texture = transformed };
+ Ao = new AoParams { Texture = original.LightingGuid };
if (Parameters.TryGetLinearColor(out var l, "Skin Boost Color And Exponent"))
{
Ao.HasColorBoost = true;
@@ -133,72 +131,94 @@ public class Material : IDisposable
"EmissiveUVPositioning (RG)UpperLeft (BA)LowerRight"))
EmissiveRegion = new Vector4(EmissiveUVs.R, EmissiveUVs.G, EmissiveUVs.B, EmissiveUVs.A);
- if ((Parameters.TryGetSwitch(out var swizzleRoughnessToGreen, "SwizzleRoughnessToGreen") && swizzleRoughnessToGreen) ||
- Parameters.Textures.ContainsKey("SRM"))
+ if ((Parameters.TryGetSwitch(out var swizzleRoughnessToGreen, "SwizzleRoughnessToGreen") && swizzleRoughnessToGreen) || Parameters.Textures.ContainsKey("SRM"))
{
foreach (var specMask in SpecularMasks)
{
- specMask.SwizzleMask = new []
+ if (AssetPool.Get().TryGet(specMask, out var bitmap))
{
- (int) PixelFormat.Red,
- (int) PixelFormat.Blue,
- (int) PixelFormat.Green,
- (int) PixelFormat.Alpha
- };
- specMask.Swizzle();
+ bitmap.SwizzleMask =
+ [
+ (int) PixelFormat.Red,
+ (int) PixelFormat.Blue,
+ (int) PixelFormat.Green,
+ (int) PixelFormat.Alpha
+ ];
+ }
}
}
}
}
}
- /// just the cache object
+ public void Setup(bool broken)
+ {
+ _handle = GL.CreateProgram();
+
+ // if (broken)
+ // {
+ // for (var i = 0; i < 1; i++)
+ // {
+ // Diffuse[i].Setup();
+ // Normals[i].Setup();
+ // }
+ // }
+ // else
+ // {
+ // foreach (var diffuse in Diffuse) diffuse.Setup();
+ // foreach (var normal in Normals) normal.Setup();
+ // foreach (var specMask in SpecularMasks) specMask.Setup();
+ // foreach (var emissive in Emissive) emissive.Setup();
+ // }
+ }
+
/// number of item in the array
/// has at least 1 clearly defined texture, else will go straight to fallback
/// list of texture parameter names by uv channel
/// fallback texture name to use if no top texture found
/// if no top texture, no fallback texture, then use the first texture found
- private Texture[] FillTextures(Options options, int uvCount, bool top, string[][] triggers, string fallback, bool first = false)
+ private FGuid[] FillTextures(int uvCount, bool top, string[][] triggers, string fallback, bool first = false)
{
UTexture original;
- Texture transformed;
var fix = fallback == CMaterialParams2.FallbackSpecularMasks;
- var textures = new Texture[uvCount];
+ var textures = new FGuid[uvCount];
if (top)
{
for (int i = 0; i < textures.Length; i++)
{
- if (Parameters.TryGetTexture2d(out original, triggers[i]) && options.TryGetTexture(original, fix, out transformed))
- textures[i] = transformed;
- else if (i > 0 && textures[i - 1] != null)
+ if (Parameters.TryGetTexture2d(out original, triggers[i]))
+ {
+ AssetPool.Get().AddTexture(original, fix);
+ textures[i] = original.LightingGuid;
+ }
+ else if (i > 0)
textures[i] = textures[i - 1];
}
}
- else if (Parameters.TryGetTexture2d(out original, fallback) && options.TryGetTexture(original, fix, out transformed))
+ else if (Parameters.TryGetTexture2d(out original, fallback))
{
+ AssetPool.Get().AddTexture(original, fix);
for (int i = 0; i < textures.Length; i++)
- textures[i] = transformed;
+ textures[i] = original.LightingGuid;
}
- else if (first && Parameters.TryGetFirstTexture2d(out original) && options.TryGetTexture(original, fix, out transformed))
+ else if (first && Parameters.TryGetFirstTexture2d(out original))
{
+ AssetPool.Get().AddTexture(original, fix);
for (int i = 0; i < textures.Length; i++)
- textures[i] = transformed;
+ textures[i] = original.LightingGuid;
}
return textures;
}
/// number of item in the array
- /// reference array
/// list of color parameter names by uv channel
/// fallback color to use if no trigger was found
- private Vector4[] FillColors(int uvCount, Texture[] textures, string[][] triggers, Vector4 fallback)
+ private Vector4[] FillColors(int uvCount, string[][] triggers, Vector4 fallback)
{
var colors = new Vector4[uvCount];
for (int i = 0; i < colors.Length; i++)
{
- if (textures[i] == null) continue;
-
if (Parameters.TryGetLinearColor(out var color, triggers[i]) && color is { A: > 0 })
{
colors[i] = new Vector4(color.R, color.G, color.B, color.A);
@@ -211,38 +231,55 @@ public class Material : IDisposable
public void Render(Shader shader)
{
var unit = 0;
+ ITexture bitmap;
+
for (var i = 0; i < Diffuse.Length; i++)
{
- shader.SetUniform($"uParameters.Diffuse[{i}].Sampler", unit);
shader.SetUniform($"uParameters.Diffuse[{i}].Color", DiffuseColor[i]);
- Diffuse[i]?.Bind(TextureUnit.Texture0 + unit++);
+ if (AssetPool.Get().TryGetTexture(Diffuse[i], out bitmap))
+ {
+ shader.SetUniform($"uParameters.Diffuse[{i}].Sampler", unit);
+ bitmap?.Bind(TextureUnit.Texture0 + unit++);
+ }
}
for (var i = 0; i < Normals.Length; i++)
{
- shader.SetUniform($"uParameters.Normals[{i}].Sampler", unit);
- Normals[i]?.Bind(TextureUnit.Texture0 + unit++);
+ if (AssetPool.Get().TryGetTexture(Normals[i], out bitmap))
+ {
+ shader.SetUniform($"uParameters.Normals[{i}].Sampler", unit);
+ bitmap?.Bind(TextureUnit.Texture0 + unit++);
+ }
}
for (var i = 0; i < SpecularMasks.Length; i++)
{
- shader.SetUniform($"uParameters.SpecularMasks[{i}].Sampler", unit);
- SpecularMasks[i]?.Bind(TextureUnit.Texture0 + unit++);
+ if (AssetPool.Get().TryGetTexture(SpecularMasks[i], out bitmap))
+ {
+ shader.SetUniform($"uParameters.SpecularMasks[{i}].Sampler", unit);
+ bitmap?.Bind(TextureUnit.Texture0 + unit++);
+ }
}
for (var i = 0; i < Emissive.Length; i++)
{
- shader.SetUniform($"uParameters.Emissive[{i}].Sampler", unit);
- shader.SetUniform($"uParameters.Emissive[{i}].Color", EmissiveColor[i]);
- Emissive[i]?.Bind(TextureUnit.Texture0 + unit++);
+ if (AssetPool.Get().TryGetTexture(Emissive[i], out bitmap))
+ {
+ shader.SetUniform($"uParameters.Emissive[{i}].Color", EmissiveColor[i]);
+ shader.SetUniform($"uParameters.Emissive[{i}].Sampler", unit);
+ bitmap?.Bind(TextureUnit.Texture0 + unit++);
+ }
}
- Ao.Texture?.Bind(TextureUnit.Texture31);
- shader.SetUniform("uParameters.Ao.Sampler", 31);
- shader.SetUniform("uParameters.Ao.HasColorBoost", Ao.HasColorBoost);
- shader.SetUniform("uParameters.Ao.ColorBoost.Color", Ao.ColorBoost.Color);
- shader.SetUniform("uParameters.Ao.ColorBoost.Exponent", Ao.ColorBoost.Exponent);
- shader.SetUniform("uParameters.HasAo", HasAo);
+ if (HasAo && AssetPool.Get().TryGetTexture(Ao.Texture, out bitmap))
+ {
+ bitmap?.Bind(TextureUnit.Texture31);
+ shader.SetUniform("uParameters.Ao.Sampler", 31);
+ shader.SetUniform("uParameters.Ao.HasColorBoost", Ao.HasColorBoost);
+ shader.SetUniform("uParameters.Ao.ColorBoost.Color", Ao.ColorBoost.Color);
+ shader.SetUniform("uParameters.Ao.ColorBoost.Exponent", Ao.ColorBoost.Exponent);
+ shader.SetUniform("uParameters.HasAo", HasAo);
+ }
shader.SetUniform("uParameters.EmissiveRegion", EmissiveRegion);
shader.SetUniform("uParameters.RoughnessMin", RoughnessMin);
@@ -311,7 +348,7 @@ public class Material : IDisposable
}
}
- public bool ImGuiTextures(Dictionary icons, UModel model)
+ public bool ImGuiTextures(Dictionary icons, UModel model)
{
if (ImGui.BeginTable("material_textures", 2))
{
@@ -340,21 +377,24 @@ public class Material : IDisposable
ImGui.EndTable();
}
- var texture = GetSelectedTexture() ?? icons["noimage"];
+ if (GetSelectedTexture() is not { } g || !AssetPool.Get().TryGetTexture(g, out var texture))
+ {
+ texture = icons["noimage"];
+ }
ImGui.Image(texture.GetPointer(),
new Vector2(ImGui.GetContentRegionAvail().X - ImGui.GetScrollX()),
Vector2.Zero, Vector2.One);
return ImGui.IsItemHovered() && ImGui.IsMouseDoubleClicked(ImGuiMouseButton.Left);
}
- public Texture GetSelectedTexture()
+ public FGuid? GetSelectedTexture()
{
return SelectedTexture switch
{
0 when Diffuse.Length > 0 => Diffuse[SelectedChannel],
1 when Normals.Length > 0 => Normals[SelectedChannel],
2 when SpecularMasks.Length > 0 => SpecularMasks[SelectedChannel],
- 3 => Ao.Texture,
+ 3 when HasAo => Ao.Texture,
4 when Emissive.Length > 0 => Emissive[SelectedChannel],
_ => null
};
@@ -371,30 +411,13 @@ public class Material : IDisposable
public void Dispose()
{
- for (int i = 0; i < Diffuse.Length; i++)
- {
- Diffuse[i]?.Dispose();
- }
- for (int i = 0; i < Normals.Length; i++)
- {
- Normals[i]?.Dispose();
- }
- for (int i = 0; i < SpecularMasks.Length; i++)
- {
- SpecularMasks[i]?.Dispose();
- }
- for (int i = 0; i < Emissive.Length; i++)
- {
- Emissive[i]?.Dispose();
- }
- Ao.Texture?.Dispose();
GL.DeleteProgram(_handle);
}
}
public struct AoParams
{
- public Texture Texture;
+ public FGuid Texture;
public Boost ColorBoost;
public bool HasColorBoost;
diff --git a/FModel/Views/Snooper/Shading/Texture.cs b/FModel/Views/Snooper/Shading/Texture.cs
deleted file mode 100644
index c425c5ee..00000000
--- a/FModel/Views/Snooper/Shading/Texture.cs
+++ /dev/null
@@ -1,316 +0,0 @@
-using System;
-using System.Numerics;
-using System.Windows;
-using CUE4Parse_Conversion.Textures;
-using CUE4Parse.UE4.Assets.Exports.Texture;
-using CUE4Parse.UE4.Objects.Core.Math;
-using CUE4Parse.UE4.Objects.Core.Misc;
-using ImGuiNET;
-using OpenTK.Graphics.OpenGL4;
-using SixLabors.ImageSharp;
-using SixLabors.ImageSharp.PixelFormats;
-using SkiaSharp;
-
-namespace FModel.Views.Snooper.Shading;
-
-public class Texture : IDisposable
-{
- private readonly int _handle;
- private readonly TextureType _type;
- private readonly TextureTarget _target;
-
- public readonly string Type;
- public readonly FGuid Guid;
- public readonly string Name;
- public readonly string Path;
- public readonly EPixelFormat Format;
- public readonly uint ImportedWidth;
- public readonly uint ImportedHeight;
- public int Width;
- public int Height;
-
- private const int DisabledChannel = (int)BlendingFactor.Zero;
- private readonly bool[] _values = [true, true, true, true];
- private readonly string[] _labels = ["R", "G", "B", "A"];
- public int[] SwizzleMask =
- [
- (int) PixelFormat.Red,
- (int) PixelFormat.Green,
- (int) PixelFormat.Blue,
- (int) PixelFormat.Alpha
- ];
-
- private Texture(TextureType type)
- {
- _handle = GL.GenTexture();
- _type = type;
- _target = _type switch
- {
- TextureType.Cubemap => TextureTarget.TextureCubeMap,
- TextureType.MsaaFramebuffer => TextureTarget.Texture2DMultisample,
- _ => TextureTarget.Texture2D
- };
-
- Guid = new FGuid();
- }
-
- public Texture(uint width, uint height) : this(TextureType.MsaaFramebuffer)
- {
- Width = (int) width;
- Height = (int) height;
- Bind(TextureUnit.Texture0);
-
- GL.TexImage2DMultisample(TextureTargetMultisample.Texture2DMultisample, Constants.SAMPLES_COUNT, PixelInternalFormat.Rgb, Width, Height, true);
- GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, _target, _handle, 0);
- }
-
- public Texture(int width, int height) : this(TextureType.Framebuffer)
- {
- Width = width;
- Height = height;
- Bind(TextureUnit.Texture0);
-
- GL.TexImage2D(_target, 0, PixelInternalFormat.Rgb, Width, Height, 0, PixelFormat.Rgb, PixelType.UnsignedByte, IntPtr.Zero);
-
- GL.TexParameter(_target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.Linear);
- GL.TexParameter(_target, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear);
- GL.TexParameter(_target, TextureParameterName.TextureWrapS, (int) TextureWrapMode.ClampToEdge);
- GL.TexParameter(_target, TextureParameterName.TextureWrapT, (int) TextureWrapMode.ClampToEdge);
-
- GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, _target, _handle, 0);
- }
-
- public Texture(SKBitmap bitmap, UTexture texture2D) : this(TextureType.Normal)
- {
- Type = texture2D.ExportType;
- Guid = texture2D.LightingGuid;
- Name = texture2D.Name;
- Path = texture2D.GetPathName();
- Format = texture2D.Format;
- Width = bitmap.Width;
- Height = bitmap.Height;
- Bind(TextureUnit.Texture0);
-
- var internalFormat = bitmap.ColorType switch
- {
- SKColorType.Gray8 => PixelInternalFormat.R8,
- _ => texture2D.SRGB ? PixelInternalFormat.Srgb : PixelInternalFormat.Rgb
- };
-
- var pixelFormat = bitmap.ColorType switch
- {
- SKColorType.Gray8 => PixelFormat.Red,
- SKColorType.Bgra8888 => PixelFormat.Bgra,
- _ => PixelFormat.Rgba
- };
-
- GL.TexImage2D(_target, 0, internalFormat, Width, Height, 0, pixelFormat, PixelType.UnsignedByte, bitmap.Bytes);
- GL.TexParameter(_target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.LinearMipmapLinear);
- GL.TexParameter(_target, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear);
- GL.TexParameter(_target, TextureParameterName.TextureBaseLevel, 0);
- GL.TexParameter(_target, TextureParameterName.TextureMaxLevel, 8);
-
- GL.GenerateMipmap(GenerateMipmapTarget.Texture2D);
- bitmap.Dispose();
- }
-
- public Texture(FLinearColor color) : this(TextureType.Normal)
- {
- Type = "LinearColor";
- Name = color.Hex;
- Width = 1;
- Height = 1;
- Bind(TextureUnit.Texture0);
-
- GL.TexImage2D(_target, 0, PixelInternalFormat.Rgba, Width, Height, 0, PixelFormat.Rgba, PixelType.Float, ref color);
- GL.TexParameter(_target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.LinearMipmapLinear);
- GL.TexParameter(_target, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear);
- GL.TexParameter(_target, TextureParameterName.TextureBaseLevel, 0);
- GL.TexParameter(_target, TextureParameterName.TextureMaxLevel, 8);
-
- GL.GenerateMipmap(GenerateMipmapTarget.Texture2D);
- }
-
- public Texture(string[] textures) : this(TextureType.Cubemap)
- {
- Bind(TextureUnit.Texture0);
-
- for (int t = 0; t < textures.Length; t++)
- {
- ProcessPixels(textures[t], TextureTarget.TextureCubeMapPositiveX + t);
- }
-
- GL.TexParameter(_target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.LinearMipmapLinear);
- GL.TexParameter(_target, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear);
- GL.TexParameter(_target, TextureParameterName.TextureWrapR, (int) TextureWrapMode.ClampToEdge);
- GL.TexParameter(_target, TextureParameterName.TextureWrapS, (int) TextureWrapMode.ClampToEdge);
- GL.TexParameter(_target, TextureParameterName.TextureWrapT, (int) TextureWrapMode.ClampToEdge);
-
- GL.GenerateMipmap(GenerateMipmapTarget.TextureCubeMap);
- }
-
- public Texture(string texture) : this(TextureType.Normal)
- {
- Bind(TextureUnit.Texture0);
-
- ProcessPixels(texture, _target);
-
- GL.TexParameter(_target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.Linear);
- GL.TexParameter(_target, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear);
- GL.TexParameter(_target, TextureParameterName.TextureWrapR, (int) TextureWrapMode.ClampToEdge);
- GL.TexParameter(_target, TextureParameterName.TextureWrapS, (int) TextureWrapMode.ClampToEdge);
- GL.TexParameter(_target, TextureParameterName.TextureWrapT, (int) TextureWrapMode.ClampToEdge);
- }
-
- private void ProcessPixels(string texture, TextureTarget target)
- {
- var info = Application.GetResourceStream(new Uri($"/FModel;component/Resources/{texture}.png", UriKind.Relative));
- using var img = Image.Load(info.Stream);
- Width = img.Width;
- Height = img.Height;
- GL.TexImage2D(target, 0, PixelInternalFormat.Rgba8, Width, Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero);
- img.ProcessPixelRows(accessor =>
- {
- for (int y = 0; y < accessor.Height; y++)
- {
- GL.TexSubImage2D(target, 0, 0, y, accessor.Width, 1, PixelFormat.Rgba, PixelType.UnsignedByte, accessor.GetRowSpan(y).ToArray());
- }
- });
- }
-
- public void Bind(TextureUnit textureSlot)
- {
- GL.ActiveTexture(textureSlot);
- Bind(_target);
- }
-
- public void Bind(TextureTarget target)
- {
- GL.BindTexture(target, _handle);
- }
-
- public void Bind()
- {
- GL.BindTexture(_target, _handle);
- }
-
- public void Swizzle()
- {
- Bind();
- GL.TexParameter(_target, TextureParameterName.TextureSwizzleRgba, SwizzleMask);
- }
-
- public IntPtr GetPointer() => (IntPtr) _handle;
-
- public void WindowResized(int width, int height)
- {
- Width = width;
- Height = height;
-
- Bind();
- switch (_type)
- {
- case TextureType.MsaaFramebuffer:
- GL.TexImage2DMultisample(TextureTargetMultisample.Texture2DMultisample, Constants.SAMPLES_COUNT, PixelInternalFormat.Rgb, Width, Height, true);
- GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, _target, _handle, 0);
- break;
- case TextureType.Framebuffer:
- GL.TexImage2D(_target, 0, PixelInternalFormat.Rgb, Width, Height, 0, PixelFormat.Rgb, PixelType.UnsignedByte, IntPtr.Zero);
- GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, _target, _handle, 0);
- break;
- default:
- throw new NotSupportedException();
- }
- }
-
- public void Dispose()
- {
- GL.DeleteTexture(_handle);
- }
-
- private Vector3 _scrolling = new (0.0f, 0.0f, 1.0f);
- public void ImGuiTextureInspector()
- {
- if (ImGui.BeginTable("texture_inspector", 2, ImGuiTableFlags.SizingStretchProp))
- {
- SnimGui.NoFramePaddingOnY(() =>
- {
- SnimGui.Layout("Type");ImGui.Text($" : ({Format}) {Name}");
- SnimGui.TooltipCopy("(?) Click to Copy Path", Path);
- SnimGui.Layout("Guid");ImGui.Text($" : {Guid.ToString(EGuidFormats.UniqueObjectGuid)}");
- SnimGui.Layout("Size");
- ImGui.Text($" : {Width}x{Height}");
-
- SnimGui.Layout("Swizzle");
- for (int c = 0; c < SwizzleMask.Length; c++)
- {
- if (ImGui.Checkbox(_labels[c], ref _values[c]))
- {
- Bind();
- GL.TexParameter(_target, TextureParameterName.TextureSwizzleR + c, _values[c] ? SwizzleMask[c] : DisabledChannel);
- }
- ImGui.SameLine();
- }
-
- ImGui.EndTable();
- });
- }
-
- var io = ImGui.GetIO();
- var canvasP0 = ImGui.GetCursorScreenPos();
- var canvasSize = ImGui.GetContentRegionAvail();
- if (canvasSize.X < 50.0f) canvasSize.X = 50.0f;
- if (canvasSize.Y < 50.0f) canvasSize.Y = 50.0f;
- var canvasP1 = canvasP0 + canvasSize;
- var origin = new Vector2(canvasP0.X + _scrolling.X, canvasP0.Y + _scrolling.Y);
- var absoluteMiddle = canvasSize / 2.0f;
-
- ImGui.InvisibleButton("texture_inspector_canvas", canvasSize, ImGuiButtonFlags.MouseButtonLeft);
- if (ImGui.IsItemActive() && ImGui.IsMouseDragging(ImGuiMouseButton.Left))
- {
- _scrolling.X += io.MouseDelta.X;
- _scrolling.Y += io.MouseDelta.Y;
- }
- else if (ImGui.IsItemHovered() && io.MouseWheel != 0.0f)
- {
- var zoomFactor = 1.0f + io.MouseWheel * 0.1f;
- var mousePosCanvas = io.MousePos - origin;
-
- _scrolling.X -= (mousePosCanvas.X - absoluteMiddle.X) * (zoomFactor - 1);
- _scrolling.Y -= (mousePosCanvas.Y - absoluteMiddle.Y) * (zoomFactor - 1);
- _scrolling.Z *= zoomFactor;
- origin = new Vector2(canvasP0.X + _scrolling.X, canvasP0.Y + _scrolling.Y);
- }
-
- var drawList = ImGui.GetWindowDrawList();
- drawList.AddRectFilled(canvasP0, canvasP1, 0xFF242424);
- drawList.PushClipRect(canvasP0, canvasP1, true);
- {
- var sensitivity = _scrolling.Z * 25.0f;
- for (float x = _scrolling.X % sensitivity; x < canvasSize.X; x += sensitivity)
- drawList.AddLine(canvasP0 with { X = canvasP0.X + x }, canvasP1 with { X = canvasP0.X + x }, 0x28C8C8C8);
- for (float y = _scrolling.Y % sensitivity; y < canvasSize.Y; y += sensitivity)
- drawList.AddLine(canvasP0 with { Y = canvasP0.Y + y }, canvasP1 with { Y = canvasP0.Y + y }, 0x28C8C8C8);
- }
- drawList.PopClipRect();
-
- drawList.PushClipRect(canvasP0, canvasP1, true);
- {
- var relativeMiddle = origin + absoluteMiddle;
- var ratio = Math.Min(canvasSize.X / Width, canvasSize.Y / Height) * 0.95f * _scrolling.Z;
- var size = new Vector2(Width, Height) * ratio / 2f;
-
- drawList.AddImage(GetPointer(), relativeMiddle - size, relativeMiddle + size);
- drawList.AddRect(relativeMiddle - size, relativeMiddle + size, 0xFFFFFFFF);
- }
- drawList.PopClipRect();
- }
-}
-
-public enum TextureType
-{
- Normal,
- Cubemap,
- Framebuffer,
- MsaaFramebuffer
-}
diff --git a/FModel/Views/Snooper/Shading/TextureHelper.cs b/FModel/Views/Snooper/Shading/TextureHelper.cs
deleted file mode 100644
index 56849b58..00000000
--- a/FModel/Views/Snooper/Shading/TextureHelper.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-using OpenTK.Graphics.OpenGL4;
-
-namespace FModel.Views.Snooper.Shading;
-
-public static class TextureHelper
-{
- ///
- /// Red : Specular (not used anymore)
- /// Green : Metallic
- /// Blue : Roughness
- ///
- public static void FixChannels(string game, Texture texture)
- {
- switch (game)
- {
- // R: Whatever (AO / S / E / ...)
- // G: Roughness
- // B: Metallic
- case "GAMEFACE":
- case "HK_PROJECT":
- case "COSMICSHAKE":
- case "PHOENIX":
- case "ATOMICHEART":
- case "MULTIVERSUS":
- case "BODYCAM":
- {
- texture.SwizzleMask =
- [
- (int) PixelFormat.Red,
- (int) PixelFormat.Blue,
- (int) PixelFormat.Green,
- (int) PixelFormat.Alpha
- ];
- break;
- }
- // R: Metallic
- // G: Roughness
- // B: Whatever (AO / S / E / ...)
- case "SHOOTERGAME":
- case "DIVINEKNOCKOUT":
- case "MOONMAN":
- {
- texture.SwizzleMask =
- [
- (int) PixelFormat.Blue,
- (int) PixelFormat.Red,
- (int) PixelFormat.Green,
- (int) PixelFormat.Alpha
- ];
- break;
- }
- // R: Roughness
- // G: Metallic
- // B: Whatever (AO / S / E / ...)
- case "CCFF7R":
- case "PJ033":
- {
- texture.SwizzleMask =
- [
- (int) PixelFormat.Blue,
- (int) PixelFormat.Green,
- (int) PixelFormat.Red,
- (int) PixelFormat.Alpha
- ];
- break;
- }
- }
- texture.Swizzle();
- }
-}
diff --git a/FModel/Views/Snooper/SnimGui.cs b/FModel/Views/Snooper/SnimGui.cs
index c8cf6f40..779543db 100644
--- a/FModel/Views/Snooper/SnimGui.cs
+++ b/FModel/Views/Snooper/SnimGui.cs
@@ -11,6 +11,7 @@ using FModel.Settings;
using FModel.Views.Snooper.Animations;
using FModel.Views.Snooper.Models;
using FModel.Views.Snooper.Shading;
+using FModel.Views.Snooper.Textures;
using ImGuizmoNET;
using OpenTK.Graphics.OpenGL4;
@@ -174,7 +175,7 @@ public class SnimGui
if (ImGui.BeginTable("world_details", 2, ImGuiTableFlags.SizingStretchProp))
{
var b = false;
- var length = s.Renderer.Options.Models.Count;
+ var length = AssetPool.Get().Models.Count;
NoFramePaddingOnY(() =>
{
@@ -184,7 +185,7 @@ public class SnimGui
if (ImGui.SmallButton("Save All"))
{
- foreach (var model in s.Renderer.Options.Models.Values)
+ foreach (var model in AssetPool.Get().Models.Values)
{
b |= model.Save(out _, out _);
}
@@ -239,12 +240,12 @@ public class SnimGui
for (int i = 0; i < s.Renderer.Options.Lights.Count; i++)
{
var light = s.Renderer.Options.Lights[i];
- var id = s.Renderer.Options.TryGetModel(light.Model, out var lightModel) ? lightModel.Name : "None";
+ var id = AssetPool.Get().TryGetModel(light.Model, out var lightModel) ? lightModel.Name : "None";
id += $"##{i}";
if (ImGui.TreeNode(id) && ImGui.BeginTable(id, 2))
{
- s.Renderer.Options.SelectModel(light.Model);
+ AssetPool.Get().SelectModel(light.Model);
light.ImGuiLight();
ImGui.EndTable();
ImGui.TreePop();
@@ -380,7 +381,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
ImGui.TableHeadersRow();
var i = 0;
- foreach ((var guid, var model) in s.Renderer.Options.Models)
+ foreach ((var guid, var model) in AssetPool.Get().Models)
{
ImGui.PushID(i);
ImGui.TableNextRow();
@@ -397,14 +398,14 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
ImGui.Text(model.UvCount.ToString("D"));
ImGui.TableNextColumn();
var doubleClick = false;
- if (ImGui.Selectable(model.Name, s.Renderer.Options.SelectedModel == guid, ImGuiSelectableFlags.SpanAllColumns | ImGuiSelectableFlags.AllowDoubleClick))
+ if (ImGui.Selectable(model.Name, AssetPool.Get().SelectedModel == guid, ImGuiSelectableFlags.SpanAllColumns | ImGuiSelectableFlags.AllowDoubleClick))
{
- s.Renderer.Options.SelectModel(guid);
+ AssetPool.Get().SelectModel(guid);
doubleClick = ImGui.IsMouseDoubleClicked(ImGuiMouseButton.Left);
}
Popup(() =>
{
- s.Renderer.Options.SelectModel(guid);
+ AssetPool.Get().SelectModel(guid);
if (ImGui.MenuItem("Show", null, model.IsVisible)) model.IsVisible = !model.IsVisible;
if (ImGui.MenuItem("Wireframe", null, model.ShowWireframe)) model.ShowWireframe = !model.ShowWireframe;
if (ImGui.MenuItem("Collisions", null, model.ShowCollisions, model.HasCollisions)) model.ShowCollisions = !model.ShowCollisions;
@@ -443,7 +444,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
doubleClick = ImGui.MenuItem("Teleport To");
if (ImGui.MenuItem("Delete")) s.Renderer.Options.RemoveModel(guid);
- if (ImGui.MenuItem("Deselect")) s.Renderer.Options.SelectModel(Guid.Empty);
+ if (ImGui.MenuItem("Deselect")) AssetPool.Get().SelectModel(Guid.Empty);
ImGui.Separator();
if (ImGui.MenuItem("Copy Path to Clipboard")) ImGui.SetClipboardText(model.Path);
});
@@ -470,8 +471,8 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
{
MeshWindow("Sockets", s.Renderer, (icons, selectedModel) =>
{
- var info = new SocketAttachementInfo { Guid = s.Renderer.Options.SelectedModel, Instance = selectedModel.SelectedInstance };
- foreach (var model in s.Renderer.Options.Models.Values)
+ var info = new SocketAttachementInfo { Guid = AssetPool.Get().SelectedModel, Instance = selectedModel.SelectedInstance };
+ foreach (var model in AssetPool.Get().Models.Values)
{
if (!model.HasSockets || model.IsSelected) continue;
if (ImGui.TreeNode($"{model.Name} [{model.Sockets.Count}]"))
@@ -511,7 +512,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
NoFramePaddingOnY(() =>
{
Layout("Entity");ImGui.Text($" : ({model.Type}) {model.Name}");
- Layout("Guid");ImGui.Text($" : {s.Renderer.Options.SelectedModel.ToString(EGuidFormats.UniqueObjectGuid)}");
+ Layout("Guid");ImGui.Text($" : {AssetPool.Get().SelectedModel.ToString(EGuidFormats.UniqueObjectGuid)}");
if (model is SkeletalModel skeletalModel)
{
Layout("Skeleton");ImGui.Text($" : {skeletalModel.Skeleton.Name}");
@@ -666,7 +667,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
ImGui.PopStyleVar();
}
- private void DrawMaterialInspector(Dictionary icons, UModel model, Section section)
+ private void DrawMaterialInspector(Dictionary icons, UModel model, Section section)
{
var material = model.Materials[section.MaterialIndex];
@@ -726,9 +727,13 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
if (!_tiOpen) return;
if (ImGui.Begin("Texture Inspector", ref _tiOpen, ImGuiWindowFlags.NoScrollbar))
{
- if (s.Renderer.Options.TryGetModel(out var model) && s.Renderer.Options.TryGetSection(model, out var section))
+ if (AssetPool.Get().TryGetModel(out var model) && s.Renderer.Options.TryGetSection(model, out var section))
{
- (model.Materials[section.MaterialIndex].GetSelectedTexture() ?? s.Renderer.Options.Icons["noimage"]).ImGuiTextureInspector();
+ if (model.Materials[section.MaterialIndex].GetSelectedTexture() is not { } g || !AssetPool.Get().TryGetTexture(g, out var texture))
+ {
+ texture = s.Renderer.Options.Icons["noimage"];
+ }
+ texture.ImGuiInspector();
}
}
ImGui.End();
@@ -741,7 +746,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
if (ImGui.Begin("Skeleton Tree", ref s.Renderer.IsSkeletonTreeOpen, ImGuiWindowFlags.NoScrollbar))
{
- if (s.Renderer.Options.TryGetModel(out var model) && model is SkeletalModel skeletalModel)
+ if (AssetPool.Get().TryGetModel(out var model) && model is SkeletalModel skeletalModel)
{
skeletalModel.Skeleton.ImGuiBoneBreadcrumb();
if (ImGui.BeginTable("skeleton_tree", 2, ImGuiTableFlags.NoSavedSettings | ImGuiTableFlags.RowBg, ImGui.GetContentRegionAvail(), ImGui.GetWindowWidth()))
@@ -793,7 +798,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
if (ImGui.IsMouseClicked(ImGuiMouseButton.Right))
{
var guid = s.Renderer.Picking.ReadPixel(ImGui.GetMousePos(), ImGui.GetCursorScreenPos(), size);
- s.Renderer.Options.SelectModel(guid);
+ AssetPool.Get().SelectModel(guid);
ImGui.SetWindowFocus("Outliner");
ImGui.SetWindowFocus("Details");
}
@@ -847,7 +852,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
private void DrawGuizmo(Snooper s)
{
- var enableGuizmo = s.Renderer.Options.TryGetModel(out var selected) && selected.IsVisible;
+ var enableGuizmo = AssetPool.Get().TryGetModel(out var selected) && selected.IsVisible;
if (enableGuizmo)
{
var view = s.Renderer.CameraOp.GetViewMatrix();
@@ -903,16 +908,16 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
ImGui.End();
}
- private void MeshWindow(string name, Renderer renderer, Action, UModel> content, bool styled = true)
+ private void MeshWindow(string name, Renderer renderer, Action, UModel> content, bool styled = true)
{
Window(name, () =>
{
- if (renderer.Options.TryGetModel(out var model)) content(renderer.Options.Icons, model);
+ if (AssetPool.Get().TryGetModel(out var model)) content(renderer.Options.Icons, model);
else NoMeshSelected();
}, styled);
}
- private void SectionWindow(string name, Renderer renderer, Action, UModel, Section> content, bool styled = true)
+ private void SectionWindow(string name, Renderer renderer, Action, UModel, Section> content, bool styled = true)
{
MeshWindow(name, renderer, (icons, model) =>
{
@@ -921,7 +926,7 @@ Snooper aims to give an accurate preview of models, materials, skeletal animatio
}, styled);
}
- private void AnimationWindow(string name, Renderer renderer, Action, TimeTracker, List> content, bool styled = true)
+ private void AnimationWindow(string name, Renderer renderer, Action, TimeTracker, List> content, bool styled = true)
{
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
Window(name, () => content(renderer.Options.Icons, renderer.Options.Tracker, renderer.Options.Animations), styled);
diff --git a/FModel/Views/Snooper/Snooper.cs b/FModel/Views/Snooper/Snooper.cs
index 5784e51c..7ac7f4f6 100644
--- a/FModel/Views/Snooper/Snooper.cs
+++ b/FModel/Views/Snooper/Snooper.cs
@@ -37,7 +37,7 @@ public class Snooper : GameWindow
public bool TryLoadExport(CancellationToken cancellationToken, UObject dummy, Lazy export)
{
Renderer.Load(cancellationToken, dummy, export);
- return Renderer.Options.Models.Count > 0;
+ return ThreadPool.PendingWorkItemCount > 0 || !AssetPool.Get().Queue.IsEmpty;
}
public unsafe void WindowShouldClose(bool value, bool clear)
diff --git a/FModel/Views/Snooper/Textures/AbstractTexture.cs b/FModel/Views/Snooper/Textures/AbstractTexture.cs
new file mode 100644
index 00000000..8df50fe1
--- /dev/null
+++ b/FModel/Views/Snooper/Textures/AbstractTexture.cs
@@ -0,0 +1,141 @@
+using System;
+using System.Numerics;
+using CUE4Parse.UE4.Objects.Core.Misc;
+using ImGuiNET;
+using OpenTK.Graphics.OpenGL4;
+
+namespace FModel.Views.Snooper.Textures;
+
+public abstract class AbstractTexture : ITexture
+{
+ public int Handle { get; private set; }
+ public TextureType Type { get; }
+ public TextureTarget Target { get; }
+ public FGuid Guid { get; protected init; }
+ public string Name { get; protected init; }
+ public int Width { get; protected set; }
+ public int Height { get; protected set; }
+
+ public AbstractTexture(TextureType type)
+ {
+ Type = type;
+ Target = Type switch
+ {
+ TextureType.Cubemap => TextureTarget.TextureCubeMap,
+ TextureType.MsaaFramebuffer => TextureTarget.Texture2DMultisample,
+ _ => TextureTarget.Texture2D
+ };
+
+ Guid = new FGuid();
+ }
+
+ public virtual void Setup()
+ {
+ Handle = GL.GenTexture();
+ Bind(TextureUnit.Texture0);
+ }
+
+ protected virtual void ImGuiInspectorHeader()
+ {
+
+ }
+
+ public void Bind(TextureUnit unit)
+ {
+ GL.ActiveTexture(unit);
+ Bind(Target);
+ }
+
+ public void Bind(TextureTarget target)
+ {
+ GL.BindTexture(target, Handle);
+ }
+
+ public void Bind()
+ {
+ GL.BindTexture(Target, Handle);
+ }
+
+ public IntPtr GetPointer() => Handle;
+
+ public void WindowResized(int width, int height)
+ {
+ Width = width;
+ Height = height;
+
+ Bind();
+ switch (Type)
+ {
+ case TextureType.MsaaFramebuffer:
+ GL.TexImage2DMultisample(TextureTargetMultisample.Texture2DMultisample, Constants.SAMPLES_COUNT, PixelInternalFormat.Rgb, Width, Height, true);
+ break;
+ case TextureType.Framebuffer:
+ GL.TexImage2D(Target, 0, PixelInternalFormat.Rgb, Width, Height, 0, PixelFormat.Rgb, PixelType.UnsignedByte, IntPtr.Zero);
+ break;
+ default:
+ throw new NotSupportedException();
+ }
+
+ GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, Target, Handle, 0);
+ }
+
+ public void Dispose()
+ {
+ GL.DeleteTexture(Handle);
+ }
+
+ private Vector3 _scrolling = new (0.0f, 0.0f, 1.0f);
+ public void ImGuiInspector()
+ {
+ ImGuiInspectorHeader();
+
+ 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();
+ }
+}
diff --git a/FModel/Views/Snooper/Textures/ApplicationTexture.cs b/FModel/Views/Snooper/Textures/ApplicationTexture.cs
new file mode 100644
index 00000000..6bd9051b
--- /dev/null
+++ b/FModel/Views/Snooper/Textures/ApplicationTexture.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Windows;
+using OpenTK.Graphics.OpenGL4;
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace FModel.Views.Snooper.Textures;
+
+public class ApplicationTexture : AbstractTexture
+{
+ private readonly Image[] _images;
+
+ public ApplicationTexture(string texture) : base(TextureType.Normal)
+ {
+ _images = [LoadImage(texture)];
+ Width = _images[0].Width;
+ Height = _images[0].Height;
+ }
+
+ public ApplicationTexture(string[] textures) : base(TextureType.Cubemap)
+ {
+ _images = new Image[textures.Length];
+ for (var i = 0; i < _images.Length; i++)
+ {
+ _images[i] = LoadImage(textures[i]);
+ }
+
+ Width = _images[0].Width;
+ Height = _images[0].Height;
+ }
+
+ public override void Setup()
+ {
+ base.Setup();
+
+ var target = Type == TextureType.Cubemap ? TextureTarget.TextureCubeMapPositiveX : Target;
+ GL.TexImage2D(target, 0, PixelInternalFormat.Rgba8, Width, Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero);
+ for (var i = 0; i < _images.Length; i++)
+ {
+ _images[i].ProcessPixelRows(accessor =>
+ {
+ for (int y = 0; y < accessor.Height; y++)
+ {
+ GL.TexSubImage2D(target + i, 0, 0, y, accessor.Width, 1, PixelFormat.Rgba, PixelType.UnsignedByte, accessor.GetRowSpan(y).ToArray());
+ }
+ });
+ }
+
+ if (Type == TextureType.Cubemap)
+ {
+ GL.TexParameter(Target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.LinearMipmapLinear);
+ }
+ else
+ {
+ GL.TexParameter(Target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.Linear);
+ }
+
+ GL.TexParameter(Target, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear);
+ GL.TexParameter(Target, TextureParameterName.TextureWrapR, (int) TextureWrapMode.ClampToEdge);
+ GL.TexParameter(Target, TextureParameterName.TextureWrapS, (int) TextureWrapMode.ClampToEdge);
+ GL.TexParameter(Target, TextureParameterName.TextureWrapT, (int) TextureWrapMode.ClampToEdge);
+
+ if (Type == TextureType.Cubemap)
+ {
+ GL.GenerateMipmap(GenerateMipmapTarget.TextureCubeMap);
+ }
+ }
+
+ private Image LoadImage(string texture)
+ {
+ var info = Application.GetResourceStream(new Uri($"/FModel;component/Resources/{texture}.png", UriKind.Relative));
+ return Image.Load(info.Stream);
+ }
+}
diff --git a/FModel/Views/Snooper/Textures/BitmapTexture.cs b/FModel/Views/Snooper/Textures/BitmapTexture.cs
new file mode 100644
index 00000000..fe9c2a1b
--- /dev/null
+++ b/FModel/Views/Snooper/Textures/BitmapTexture.cs
@@ -0,0 +1,161 @@
+using CUE4Parse.UE4.Assets.Exports.Texture;
+using CUE4Parse.UE4.Objects.Core.Misc;
+using ImGuiNET;
+using OpenTK.Graphics.OpenGL4;
+using SkiaSharp;
+
+namespace FModel.Views.Snooper.Textures;
+
+public class BitmapTexture : AbstractTexture
+{
+ private readonly byte[] _bytes;
+ private readonly SKColorType _type;
+ private readonly bool _isSrgb;
+
+ 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 readonly string Path;
+ public readonly EPixelFormat Format;
+
+ public BitmapTexture(SKBitmap bitmap, UTexture texture) : base(TextureType.Normal)
+ {
+ Guid = texture.LightingGuid;
+ Name = texture.Name;
+ Width = bitmap.Width;
+ Height = bitmap.Height;
+
+ Path = texture.GetPathName();
+ Format = texture.Format;
+
+ _bytes = bitmap.Bytes;
+ _type = bitmap.ColorType;
+ _isSrgb = texture.SRGB;
+
+ bitmap.Dispose();
+ }
+
+ public override void Setup()
+ {
+ base.Setup();
+
+ var internalFormat = _type switch
+ {
+ SKColorType.Gray8 => PixelInternalFormat.R8,
+ _ => _isSrgb ? PixelInternalFormat.Srgb : PixelInternalFormat.Rgb
+ };
+
+ var pixelFormat = _type switch
+ {
+ SKColorType.Gray8 => PixelFormat.Red,
+ SKColorType.Bgra8888 => PixelFormat.Bgra,
+ _ => PixelFormat.Rgba
+ };
+
+ GL.TexImage2D(Target, 0, internalFormat, Width, Height, 0, pixelFormat, PixelType.UnsignedByte, _bytes);
+ GL.TexParameter(Target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.LinearMipmapLinear);
+ GL.TexParameter(Target, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear);
+ GL.TexParameter(Target, TextureParameterName.TextureBaseLevel, 0);
+ GL.TexParameter(Target, TextureParameterName.TextureMaxLevel, 8);
+
+ GL.TexParameter(Target, TextureParameterName.TextureSwizzleRgba, SwizzleMask);
+
+ GL.GenerateMipmap(GenerateMipmapTarget.Texture2D);
+ }
+
+ protected override void ImGuiInspectorHeader()
+ {
+ base.ImGuiInspectorHeader();
+
+ if (ImGui.BeginTable("texture_inspector", 2, ImGuiTableFlags.SizingStretchProp))
+ {
+ SnimGui.NoFramePaddingOnY(() =>
+ {
+ SnimGui.Layout("Type");ImGui.Text($" : ({Format}) {Name}");
+ SnimGui.TooltipCopy("(?) Click to Copy Path", Path);
+ SnimGui.Layout("Guid");ImGui.Text($" : {Guid.ToString(EGuidFormats.UniqueObjectGuid)}");
+ SnimGui.Layout("Size");
+ ImGui.Text($" : {Width}x{Height}");
+
+ SnimGui.Layout("Swizzle");
+ for (int c = 0; c < SwizzleMask.Length; c++)
+ {
+ if (ImGui.Checkbox(_labels[c], ref _values[c]))
+ {
+ Bind();
+ GL.TexParameter(Target, TextureParameterName.TextureSwizzleR + c, _values[c] ? SwizzleMask[c] : DisabledChannel);
+ }
+ ImGui.SameLine();
+ }
+
+ ImGui.EndTable();
+ });
+ }
+ }
+
+ public void FixChannels(string project)
+ {
+ switch (project)
+ {
+ // R: Whatever (AO / S / E / ...)
+ // G: Roughness
+ // B: Metallic
+ case "GAMEFACE":
+ case "HK_PROJECT":
+ case "COSMICSHAKE":
+ case "PHOENIX":
+ case "ATOMICHEART":
+ case "MULTIVERSUS":
+ case "BODYCAM":
+ {
+ SwizzleMask =
+ [
+ (int) PixelFormat.Red,
+ (int) PixelFormat.Blue,
+ (int) PixelFormat.Green,
+ (int) PixelFormat.Alpha
+ ];
+ break;
+ }
+ // R: Metallic
+ // G: Roughness
+ // B: Whatever (AO / S / E / ...)
+ case "SHOOTERGAME":
+ case "DIVINEKNOCKOUT":
+ case "MOONMAN":
+ {
+ SwizzleMask =
+ [
+ (int) PixelFormat.Blue,
+ (int) PixelFormat.Red,
+ (int) PixelFormat.Green,
+ (int) PixelFormat.Alpha
+ ];
+ break;
+ }
+ // R: Roughness
+ // G: Metallic
+ // B: Whatever (AO / S / E / ...)
+ case "CCFF7R":
+ case "PJ033":
+ {
+ SwizzleMask =
+ [
+ (int) PixelFormat.Blue,
+ (int) PixelFormat.Green,
+ (int) PixelFormat.Red,
+ (int) PixelFormat.Alpha
+ ];
+ break;
+ }
+ }
+ }
+}
diff --git a/FModel/Views/Snooper/Textures/FramebufferTexture.cs b/FModel/Views/Snooper/Textures/FramebufferTexture.cs
new file mode 100644
index 00000000..a851272a
--- /dev/null
+++ b/FModel/Views/Snooper/Textures/FramebufferTexture.cs
@@ -0,0 +1,27 @@
+using System;
+using OpenTK.Graphics.OpenGL4;
+
+namespace FModel.Views.Snooper.Textures;
+
+public class FramebufferTexture : AbstractTexture
+{
+ public FramebufferTexture(int width, int height) : base(TextureType.Framebuffer)
+ {
+ Width = width;
+ Height = height;
+ }
+
+ public override void Setup()
+ {
+ base.Setup();
+
+ GL.TexImage2D(Target, 0, PixelInternalFormat.Rgb, Width, Height, 0, PixelFormat.Rgb, PixelType.UnsignedByte, IntPtr.Zero);
+
+ GL.TexParameter(Target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.Linear);
+ GL.TexParameter(Target, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear);
+ GL.TexParameter(Target, TextureParameterName.TextureWrapS, (int) TextureWrapMode.ClampToEdge);
+ GL.TexParameter(Target, TextureParameterName.TextureWrapT, (int) TextureWrapMode.ClampToEdge);
+
+ GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, Target, Handle, 0);
+ }
+}
diff --git a/FModel/Views/Snooper/Textures/ITexture.cs b/FModel/Views/Snooper/Textures/ITexture.cs
new file mode 100644
index 00000000..c2bd4589
--- /dev/null
+++ b/FModel/Views/Snooper/Textures/ITexture.cs
@@ -0,0 +1,31 @@
+using System;
+using CUE4Parse.UE4.Objects.Core.Misc;
+using FModel.Views.Snooper.Models;
+using OpenTK.Graphics.OpenGL4;
+
+namespace FModel.Views.Snooper.Textures;
+
+public interface ITexture : IDelayedSetup
+{
+ public int Handle { get; }
+ public TextureType Type { get; }
+ public TextureTarget Target { get; }
+
+ public string Name { get; }
+ public int Width { get; }
+ public int Height { get; }
+
+ public void Bind(TextureUnit unit);
+ public void Bind(TextureTarget target);
+ public IntPtr GetPointer();
+
+ public void ImGuiInspector();
+}
+
+public enum TextureType
+{
+ Normal,
+ Cubemap,
+ Framebuffer,
+ MsaaFramebuffer
+}
diff --git a/FModel/Views/Snooper/Textures/LinearColorTexture.cs b/FModel/Views/Snooper/Textures/LinearColorTexture.cs
new file mode 100644
index 00000000..5213a6eb
--- /dev/null
+++ b/FModel/Views/Snooper/Textures/LinearColorTexture.cs
@@ -0,0 +1,33 @@
+using CUE4Parse.UE4.Objects.Core.Math;
+using CUE4Parse.UE4.Objects.Core.Misc;
+using OpenTK.Graphics.OpenGL4;
+
+namespace FModel.Views.Snooper.Textures;
+
+public class LinearColorTexture : AbstractTexture
+{
+ private FLinearColor _color;
+
+ public LinearColorTexture(FLinearColor color) : base(TextureType.Normal)
+ {
+ _color = color;
+
+ // Guid = new FGuid(_color.Hex);
+ Name = _color.Hex;
+ Width = 1;
+ Height = 1;
+ }
+
+ public override void Setup()
+ {
+ base.Setup();
+
+ GL.TexImage2D(Target, 0, PixelInternalFormat.Rgba, Width, Height, 0, PixelFormat.Rgba, PixelType.Float, ref _color);
+ GL.TexParameter(Target, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.LinearMipmapLinear);
+ GL.TexParameter(Target, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Linear);
+ GL.TexParameter(Target, TextureParameterName.TextureBaseLevel, 0);
+ GL.TexParameter(Target, TextureParameterName.TextureMaxLevel, 8);
+
+ GL.GenerateMipmap(GenerateMipmapTarget.Texture2D);
+ }
+}
diff --git a/FModel/Views/Snooper/Textures/MsaaTexture.cs b/FModel/Views/Snooper/Textures/MsaaTexture.cs
new file mode 100644
index 00000000..c936c428
--- /dev/null
+++ b/FModel/Views/Snooper/Textures/MsaaTexture.cs
@@ -0,0 +1,20 @@
+using OpenTK.Graphics.OpenGL4;
+
+namespace FModel.Views.Snooper.Textures;
+
+public class MsaaTexture : AbstractTexture
+{
+ public MsaaTexture(uint width, uint height) : base(TextureType.MsaaFramebuffer)
+ {
+ Width = (int) width;
+ Height = (int) height;
+ }
+
+ public override void Setup()
+ {
+ base.Setup();
+
+ GL.TexImage2DMultisample(TextureTargetMultisample.Texture2DMultisample, Constants.SAMPLES_COUNT, PixelInternalFormat.Rgb, Width, Height, true);
+ GL.FramebufferTexture2D(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, Target, Handle, 0);
+ }
+}