From 9781446aef2f843b615af229660858d378e37abf Mon Sep 17 00:00:00 2001 From: Asval Date: Sat, 25 May 2024 00:19:47 +0200 Subject: [PATCH] juno building preview --- CUE4Parse | 2 +- FModel/Creator/CreatorPackage.cs | 1 + FModel/Extensions/StringExtensions.cs | 21 ++- FModel/Resources/default.vert | 2 +- FModel/ViewModels/CUE4ParseViewModel.cs | 26 ++-- FModel/ViewModels/TabControlViewModel.cs | 30 ++-- .../Controls/Aed/GamePathElementGenerator.cs | 9 +- .../Controls/Aed/GamePathVisualLineText.cs | 18 ++- .../Resources/Controls/AvalonEditor.xaml.cs | 2 +- FModel/Views/Snooper/Renderer.cs | 147 +++++++++++++++++- FModel/Views/Snooper/Shading/Material.cs | 4 +- 11 files changed, 208 insertions(+), 54 deletions(-) diff --git a/CUE4Parse b/CUE4Parse index 52292cb8..0c12be3c 160000 --- a/CUE4Parse +++ b/CUE4Parse @@ -1 +1 @@ -Subproject commit 52292cb88d4de990fbcf6e07fac883e3a8d13d4f +Subproject commit 0c12be3c624d0e75b18b0a03915442610de54672 diff --git a/FModel/Creator/CreatorPackage.cs b/FModel/Creator/CreatorPackage.cs index 07530d7f..30788ed4 100644 --- a/FModel/Creator/CreatorPackage.cs +++ b/FModel/Creator/CreatorPackage.cs @@ -112,6 +112,7 @@ public bool TryConstructCreator(out UCreator creator) case "FortConversionControlItemDefinition": case "FortAccountBuffCreditItemDefinition": case "JunoBuildInstructionsItemDefinition": + case "JunoBuildingSetAccountItemDefinition": case "FortEventCurrencyItemDefinitionRedir": case "FortPersistentResourceItemDefinition": case "FortWeaponMeleeOffhandItemDefinition": diff --git a/FModel/Extensions/StringExtensions.cs b/FModel/Extensions/StringExtensions.cs index 67dec4ba..95342438 100644 --- a/FModel/Extensions/StringExtensions.cs +++ b/FModel/Extensions/StringExtensions.cs @@ -2,6 +2,7 @@ using System.IO; using System.Runtime.CompilerServices; using System.Text.RegularExpressions; +using ICSharpCode.AvalonEdit.Document; namespace FModel.Extensions; @@ -94,7 +95,7 @@ public static string SubstringAfterLast(this string s, string delimiter, StringC } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetLineNumber(this string s, string lineToFind) + public static int GetNameLineNumber(this string s, string lineToFind) { if (int.TryParse(lineToFind, out var index)) return s.GetLineNumber(index); @@ -113,6 +114,24 @@ public static int GetLineNumber(this string s, string lineToFind) return 1; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string GetParentExportType(this TextDocument doc, int startOffset) + { + var line = doc.GetLineByOffset(startOffset); + var lineNumber = line.LineNumber - 1; + + while (doc.GetText(line.Offset, line.Length) is { } content) + { + if (content.StartsWith(" \"Type\": \"", StringComparison.OrdinalIgnoreCase)) + return content.Split("\"")[3]; + + lineNumber--; + line = doc.GetLineByNumber(lineNumber); + } + + return string.Empty; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int GetKismetLineNumber(this string s, string input) { diff --git a/FModel/Resources/default.vert b/FModel/Resources/default.vert index 6fa19706..9f396843 100644 --- a/FModel/Resources/default.vert +++ b/FModel/Resources/default.vert @@ -95,5 +95,5 @@ void main() fTangent = vec3(transpose(inverse(vInstanceMatrix)) * finalTangent); fTexCoords = vTexCoords; fTexLayer = vTexLayer; - fColor = unpackARGB(int(vColor)); + fColor = unpackARGB(int(vColor)) / 255.0; } diff --git a/FModel/ViewModels/CUE4ParseViewModel.cs b/FModel/ViewModels/CUE4ParseViewModel.cs index 0eac6eea..aff3c91e 100644 --- a/FModel/ViewModels/CUE4ParseViewModel.cs +++ b/FModel/ViewModels/CUE4ParseViewModel.cs @@ -609,23 +609,13 @@ public void Extract(CancellationToken cancellationToken, string fullPath, bool a var fileName = fullPath.SubstringAfterLast('/'); var ext = fullPath.SubstringAfterLast('.').ToLower(); - if (addNewTab && TabControl.CanAddTabs) - { - TabControl.AddTab(fileName, directory); - } - else - { - TabControl.SelectedTab.Header = fileName; - TabControl.SelectedTab.Directory = directory; - } + if (addNewTab && TabControl.CanAddTabs) TabControl.AddTab(fileName, directory); + else TabControl.SelectedTab.SoftReset(fileName, directory); + TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector(ext); var updateUi = !HasFlag(bulk, EBulkType.Auto); var saveProperties = HasFlag(bulk, EBulkType.Properties); var saveTextures = HasFlag(bulk, EBulkType.Textures); - TabControl.SelectedTab.ClearImages(); - TabControl.SelectedTab.ResetDocumentText(); - TabControl.SelectedTab.ScrollTrigger = null; - TabControl.SelectedTab.Highlighter = AvalonExtensions.HighlighterSelector(ext); switch (ext) { case "uasset": @@ -801,10 +791,10 @@ public void Extract(CancellationToken cancellationToken, string fullPath, bool a } } - public void ExtractAndScroll(CancellationToken cancellationToken, string fullPath, string objectName) + public void ExtractAndScroll(CancellationToken cancellationToken, string fullPath, string objectName, string parentExportType) { Log.Information("User CTRL-CLICKED to extract '{FullPath}'", fullPath); - TabControl.AddTab(fullPath.SubstringAfterLast('/'), fullPath.SubstringBeforeLast('/')); + TabControl.AddTab(fullPath.SubstringAfterLast('/'), fullPath.SubstringBeforeLast('/'), parentExportType); TabControl.SelectedTab.ScrollTrigger = objectName; var exports = Provider.LoadAllObjects(fullPath); @@ -855,6 +845,12 @@ public void ExtractAndScroll(CancellationToken cancellationToken, string fullPat return false; } case UWorld when isNone && UserSettings.Default.PreviewWorlds: + case UBlueprintGeneratedClass when isNone && UserSettings.Default.PreviewWorlds && TabControl.SelectedTab.ParentExportType switch + { + "JunoBuildInstructionsItemDefinition" => true, + "JunoBuildingSetAccountItemDefinition" => true, + _ => false + }: case UAtomModel when isNone && UserSettings.Default.PreviewStaticMeshes: case UStaticMesh when isNone && UserSettings.Default.PreviewStaticMeshes: case USkeletalMesh when isNone && UserSettings.Default.PreviewSkeletalMeshes: diff --git a/FModel/ViewModels/TabControlViewModel.cs b/FModel/ViewModels/TabControlViewModel.cs index 8d081946..27924c4d 100644 --- a/FModel/ViewModels/TabControlViewModel.cs +++ b/FModel/ViewModels/TabControlViewModel.cs @@ -86,6 +86,8 @@ private void SetImage(SKBitmap bitmap) public class TabItem : ViewModel { + public string ParentExportType { get; private set; } + private string _header; public string Header { @@ -211,20 +213,28 @@ public TabImage SelectedImage private GoToCommand _goToCommand; public GoToCommand GoToCommand => _goToCommand ??= new GoToCommand(null); - public TabItem(string header, string directory) + public TabItem(string header, string directory, string parentExportType) { Header = header; Directory = directory; + ParentExportType = parentExportType; _images = new ObservableCollection(); } - public void ClearImages() + public void SoftReset(string header, string directory) { + Header = header; + Directory = directory; + ParentExportType = string.Empty; + ScrollTrigger = null; Application.Current.Dispatcher.Invoke(() => { _images.Clear(); SelectedImage = null; RaisePropertyChanged("HasMultipleImages"); + + Document ??= new TextDocument(); + Document.Text = string.Empty; }); } @@ -274,15 +284,6 @@ public void SetDocumentText(string text, bool save, bool updateUi) }); } - public void ResetDocumentText() - { - Application.Current.Dispatcher.Invoke(() => - { - Document ??= new TextDocument(); - Document.Text = string.Empty; - }); - } - public void SaveImage() => SaveImage(SelectedImage, true); private void SaveImage(TabImage image, bool updateUi) { @@ -368,12 +369,13 @@ public TabControlViewModel() SelectedTab = TabsItems.FirstOrDefault(); } - public void AddTab(string header = null, string directory = null) + public void AddTab(string header = null, string directory = null, string parentExportType = null) { if (!CanAddTabs) return; var h = header ?? "New Tab"; var d = directory ?? string.Empty; + var p = parentExportType ?? string.Empty; if (SelectedTab is { Header : "New Tab" }) { SelectedTab.Header = h; @@ -383,7 +385,7 @@ public void AddTab(string header = null, string directory = null) Application.Current.Dispatcher.Invoke(() => { - _tabItems.Add(new TabItem(h, d)); + _tabItems.Add(new TabItem(h, d, p)); SelectedTab = _tabItems.Last(); }); } @@ -445,6 +447,6 @@ public void RemoveAllTabs() private static IEnumerable EnumerateTabs() { - yield return new TabItem("New Tab", string.Empty); + yield return new TabItem("New Tab", string.Empty, string.Empty); } } diff --git a/FModel/Views/Resources/Controls/Aed/GamePathElementGenerator.cs b/FModel/Views/Resources/Controls/Aed/GamePathElementGenerator.cs index 914309e8..dbf83c0a 100644 --- a/FModel/Views/Resources/Controls/Aed/GamePathElementGenerator.cs +++ b/FModel/Views/Resources/Controls/Aed/GamePathElementGenerator.cs @@ -1,4 +1,5 @@ using System.Text.RegularExpressions; +using FModel.Extensions; using ICSharpCode.AvalonEdit.Rendering; namespace FModel.Views.Resources.Controls; @@ -29,8 +30,10 @@ public override int GetFirstInterestedOffset(int startOffset) public override VisualLineElement ConstructElement(int offset) { var m = FindMatch(offset); - if (!m.Success || m.Index != 0) return null; + if (!m.Success || m.Index != 0 || + !m.Groups.TryGetValue("target", out var g)) return null; - return m.Groups.TryGetValue("target", out var g) ? new GamePathVisualLineText(g.Value, CurrentContext.VisualLine, g.Length + g.Index + 1) : null; + var parentExportType = CurrentContext.Document.GetParentExportType(offset); + return new GamePathVisualLineText(g.Value, parentExportType, CurrentContext.VisualLine, g.Length + g.Index + 1); } -} \ No newline at end of file +} diff --git a/FModel/Views/Resources/Controls/Aed/GamePathVisualLineText.cs b/FModel/Views/Resources/Controls/Aed/GamePathVisualLineText.cs index 426891a0..d0dedb59 100644 --- a/FModel/Views/Resources/Controls/Aed/GamePathVisualLineText.cs +++ b/FModel/Views/Resources/Controls/Aed/GamePathVisualLineText.cs @@ -16,14 +16,16 @@ public class GamePathVisualLineText : VisualLineText private ThreadWorkerViewModel _threadWorkerView => ApplicationService.ThreadWorkerView; private ApplicationViewModel _applicationView => ApplicationService.ApplicationView; - public delegate void GamePathOnClick(string gamePath); + public delegate void GamePathOnClick(string gamePath, string parentExportType); public event GamePathOnClick OnGamePathClicked; private readonly string _gamePath; + private readonly string _parentExportType; - public GamePathVisualLineText(string gamePath, VisualLine parentVisualLine, int length) : base(parentVisualLine, length) + public GamePathVisualLineText(string gamePath, string parentExportType, VisualLine parentVisualLine, int length) : base(parentVisualLine, length) { _gamePath = gamePath; + _parentExportType = parentExportType; } public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context) @@ -56,14 +58,14 @@ protected override void OnMouseDown(MouseButtonEventArgs e) if (e.Handled || OnGamePathClicked == null) return; - OnGamePathClicked(_gamePath); + OnGamePathClicked(_gamePath, _parentExportType); e.Handled = true; } protected override VisualLineText CreateInstance(int length) { - var a = new GamePathVisualLineText(_gamePath, ParentVisualLine, length); - a.OnGamePathClicked += async gamePath => + var a = new GamePathVisualLineText(_gamePath, _parentExportType, ParentVisualLine, length); + a.OnGamePathClicked += async (gamePath, parentExportType) => { var obj = gamePath.SubstringAfterLast('.'); var package = gamePath.SubstringBeforeLast('.'); @@ -80,17 +82,17 @@ protected override VisualLineText CreateInstance(int length) } else { - lineNumber = a.ParentVisualLine.Document.Text.GetLineNumber(obj); + lineNumber = a.ParentVisualLine.Document.Text.GetNameLineNumber(obj); line = a.ParentVisualLine.Document.GetLineByNumber(lineNumber); } - + AvalonEditor.YesWeEditor.Select(line.Offset, line.Length); AvalonEditor.YesWeEditor.ScrollToLine(lineNumber); } else { await _threadWorkerView.Begin(cancellationToken => - _applicationView.CUE4Parse.ExtractAndScroll(cancellationToken, fullPath, obj)); + _applicationView.CUE4Parse.ExtractAndScroll(cancellationToken, fullPath, obj, parentExportType)); } }; return a; diff --git a/FModel/Views/Resources/Controls/AvalonEditor.xaml.cs b/FModel/Views/Resources/Controls/AvalonEditor.xaml.cs index c145a849..f09fa3f8 100644 --- a/FModel/Views/Resources/Controls/AvalonEditor.xaml.cs +++ b/FModel/Views/Resources/Controls/AvalonEditor.xaml.cs @@ -126,7 +126,7 @@ private void OnTextChanged(object sender, EventArgs e) if (!tabItem.ShouldScroll) return; - var lineNumber = avalonEditor.Document.Text.GetLineNumber(tabItem.ScrollTrigger); + var lineNumber = avalonEditor.Document.Text.GetNameLineNumber(tabItem.ScrollTrigger); var line = avalonEditor.Document.GetLineByNumber(lineNumber); avalonEditor.Select(line.Offset, line.Length); avalonEditor.ScrollToLine(lineNumber); diff --git a/FModel/Views/Snooper/Renderer.cs b/FModel/Views/Snooper/Renderer.cs index 7e6035d4..a623a065 100644 --- a/FModel/Views/Snooper/Renderer.cs +++ b/FModel/Views/Snooper/Renderer.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Numerics; using System.Threading; @@ -9,6 +10,7 @@ using CUE4Parse.UE4.Assets.Exports.Animation; using CUE4Parse.UE4.Assets.Exports.Atom; using CUE4Parse.UE4.Assets.Exports.Component.StaticMesh; +using CUE4Parse.UE4.Assets.Exports.GeometryCollection; using CUE4Parse.UE4.Assets.Exports.Material; using CUE4Parse.UE4.Assets.Exports.SkeletalMesh; using CUE4Parse.UE4.Assets.Exports.StaticMesh; @@ -79,8 +81,8 @@ public Renderer(int width, int height) public void Load(CancellationToken cancellationToken, UObject export) { ShowLights = false; - _saveCameraMode = export is not UWorld; - CameraOp.Mode = _saveCameraMode ? UserSettings.Default.CameraMode : Camera.WorldMode.FlyCam; + Color = VertexColor.Default; + _saveCameraMode = export is not UWorld and not UBlueprintGeneratedClass; switch (export) { case UStaticMesh st: @@ -98,10 +100,15 @@ public void Load(CancellationToken cancellationToken, UObject export) case UWorld wd: LoadWorld(cancellationToken, wd, Transform.Identity); break; + case UBlueprintGeneratedClass bp: + LoadJunoWorld(cancellationToken, bp, Transform.Identity); + Color = VertexColor.Colors; + break; case UAtomModel at: LoadAtom(cancellationToken, at); break; } + CameraOp.Mode = _saveCameraMode ? UserSettings.Default.CameraMode : Camera.WorldMode.FlyCam; SetupCamera(); } @@ -433,6 +440,49 @@ private void LoadWorld(CancellationToken cancellationToken, UWorld original, Tra Services.ApplicationService.ApplicationView.Status.UpdateStatusLabel($"{original.Name} ... {length}/{length}"); } + private void LoadJunoWorld(CancellationToken cancellationToken, UBlueprintGeneratedClass original, Transform transform) + { + CameraOp.Setup(new FBox(FVector.ZeroVector, new FVector(0, 10, 10))); + + var length = 0; + FPackageIndex[] allNodes = []; + IPropertyHolder[] records = []; + if (original.TryGetValue(out FPackageIndex simpleConstructionScript, "SimpleConstructionScript") && + simpleConstructionScript.TryLoad(out var scs) && scs.TryGetValue(out allNodes, "AllNodes")) + length = allNodes.Length; + else if (original.TryGetValue(out FPackageIndex inheritableComponentHandler, "InheritableComponentHandler") && + inheritableComponentHandler.TryLoad(out var ich) && ich.TryGetValue(out records, "Records")) + length = records.Length; + + for (var i = 0; i < length; i++) + { + cancellationToken.ThrowIfCancellationRequested(); + + IPropertyHolder actor; + if (allNodes is {Length: > 0} && allNodes[i].TryLoad(out UObject node)) + { + actor = node; + } + else if (records is {Length: > 0}) + { + actor = records[i]; + } + else continue; + + Services.ApplicationService.ApplicationView.Status.UpdateStatusLabel($"{original.Name} ... {i}/{length}"); + WorldMesh(actor, transform, true); + } + Services.ApplicationService.ApplicationView.Status.UpdateStatusLabel($"{original.Name} ... {length}/{length}"); + + if (Options.Models.Count == 1) + { + var (guid, model) = Options.Models.First(); + Options.SelectModel(guid); + CameraOp.Setup(model.Box); + _saveCameraMode = true; + } + } + private void WorldCamera(UObject actor) { if (actor.ExportType != "LevelBounds" || !actor.TryGetValue(out FPackageIndex boxComponent, "BoxComponent") || @@ -463,7 +513,7 @@ private void WorldLight(UObject actor) } } - private void WorldMesh(UObject actor, Transform transform) + private void WorldMesh(IPropertyHolder actor, Transform transform, bool forceShow = false) { if (actor.TryGetValue(out FPackageIndex[] instanceComponents, "InstanceComponents")) { @@ -491,7 +541,24 @@ private void WorldMesh(UObject actor, Transform transform) else ProcessMesh(actor, staticMeshComp, m, CalculateTransform(staticMeshComp, transform)); } } - else if (actor.TryGetValue(out FPackageIndex staticMeshComponent, "StaticMeshComponent", "StaticMesh", "Mesh", "LightMesh") && + else if (actor.TryGetValue(out FPackageIndex componentTemplate, "ComponentTemplate") && + componentTemplate.TryLoad(out UObject compTemplate)) + { + UGeometryCollection geometryCollection = null; + if (!compTemplate.TryGetValue(out UStaticMesh m, "StaticMesh") && + compTemplate.TryGetValue(out FPackageIndex restCollection, "RestCollection") && + restCollection.TryLoad(out geometryCollection) && geometryCollection.RootProxyData is { ProxyMeshes.Length: > 0 } rootProxyData) + { + rootProxyData.ProxyMeshes[0].TryLoad(out m); + } + + if (m is { Materials.Length: > 0 }) + { + OverrideJunoVertexColors(m, geometryCollection); + ProcessMesh(actor, compTemplate, m, CalculateTransform(compTemplate, transform), forceShow); + } + } + else if (actor.TryGetValue(out FPackageIndex staticMeshComponent, "StaticMeshComponent", "ComponentTemplate", "StaticMesh", "Mesh", "LightMesh") && staticMeshComponent.TryLoad(out UStaticMeshComponent staticMeshComp) && staticMeshComp.GetStaticMesh().TryLoad(out UStaticMesh m) && m.Materials.Length > 0) { @@ -499,11 +566,14 @@ private void WorldMesh(UObject actor, Transform transform) } } - private void ProcessMesh(UObject actor, UStaticMeshComponent staticMeshComp, UStaticMesh m, Transform transform) + private void ProcessMesh(IPropertyHolder actor, UStaticMeshComponent staticMeshComp, UStaticMesh m, Transform transform) { - var guid = m.LightingGuid; - OverrideVertexColors(staticMeshComp, m); + ProcessMesh(actor, staticMeshComp, m, transform, false); + } + private void ProcessMesh(IPropertyHolder actor, UObject staticMeshComp, UStaticMesh m, Transform transform, bool forceShow) + { + var guid = m.LightingGuid; if (Options.TryGetModel(guid, out var model)) { model.AddInstance(transform); @@ -561,6 +631,13 @@ private void ProcessMesh(UObject actor, UStaticMeshComponent staticMeshComp, USt } } + if (forceShow) + { + foreach (var section in model.Sections) + { + section.Show = true; + } + } Options.Models[guid] = model; } @@ -576,7 +653,7 @@ private void ProcessMesh(UObject actor, UStaticMeshComponent staticMeshComp, USt } } - private Transform CalculateTransform(UStaticMeshComponent staticMeshComp, Transform relation) + private Transform CalculateTransform(IPropertyHolder staticMeshComp, Transform relation) { return new Transform { @@ -587,6 +664,60 @@ private Transform CalculateTransform(UStaticMeshComponent staticMeshComp, Transf }; } + private void OverrideJunoVertexColors(UStaticMesh staticMesh, UGeometryCollection geometryCollection = null) + { + if (staticMesh.RenderData is not { LODs.Length: > 0 } || staticMesh.RenderData.LODs[0].ColorVertexBuffer == null) + return; + + var dico = new Dictionary(); + if (geometryCollection?.Materials is not { Length: > 0 }) + { + var distinctReds = new HashSet(); + for (int i = 0; i < staticMesh.RenderData.LODs[0].ColorVertexBuffer.Data.Length; i++) + { + ref var vertexColor = ref staticMesh.RenderData.LODs[0].ColorVertexBuffer.Data[i]; + var indexAsByte = vertexColor.R; + if (vertexColor.R == 255) indexAsByte = vertexColor.A; + distinctReds.Add(indexAsByte); + } + + foreach (var indexAsByte in distinctReds) + { + var path = string.Concat("/JunoAtomAssets/Materials/MI_LegoStandard_", indexAsByte, ".MI_LegoStandard_", indexAsByte); + if (!Utils.TryLoadObject(path, out UMaterialInterface unrealMaterial)) + continue; + + var parameters = new CMaterialParams2(); + unrealMaterial.GetParams(parameters, EMaterialFormat.FirstLayer); + + if (!parameters.TryGetLinearColor(out var color, "Color")) + color = FLinearColor.Gray; + + dico[indexAsByte] = color.ToFColor(true); + } + } + else foreach (var material in geometryCollection.Materials) + { + if (!material.TryLoad(out UMaterialInterface unrealMaterial)) continue; + + var parameters = new CMaterialParams2(); + unrealMaterial.GetParams(parameters, EMaterialFormat.FirstLayer); + + if (!byte.TryParse(material.Name.SubstringAfterLast("_"), out var indexAsByte)) + indexAsByte = byte.MaxValue; + if (!parameters.TryGetLinearColor(out var color, "Color")) + color = FLinearColor.Gray; + + dico[indexAsByte] = color.ToFColor(true); + } + + for (int i = 0; i < staticMesh.RenderData.LODs[0].ColorVertexBuffer.Data.Length; i++) + { + ref var vertexColor = ref staticMesh.RenderData.LODs[0].ColorVertexBuffer.Data[i]; + vertexColor = dico.TryGetValue(vertexColor.R, out var color) ? color : FColor.Gray; + } + } + private void OverrideVertexColors(UStaticMeshComponent staticMeshComp, UStaticMesh staticMesh) { if (staticMeshComp.LODData is not { Length: > 0 } || staticMesh.RenderData is not { LODs.Length: > 0 }) diff --git a/FModel/Views/Snooper/Shading/Material.cs b/FModel/Views/Snooper/Shading/Material.cs index eefbf38a..08eeffe5 100644 --- a/FModel/Views/Snooper/Shading/Material.cs +++ b/FModel/Views/Snooper/Shading/Material.cs @@ -76,8 +76,8 @@ public void Setup(Options options, int uvCount) if (uvCount < 1 || Parameters.IsNull) { - Diffuse = [new Texture(new FLinearColor(.6f, .6f, .6f, 1f))]; - Normals = [new Texture(new FLinearColor(0.498f, 0.498f, 0.996f, 1f))]; + 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 = [Vector4.One];