From 5efd8c5c88ecd08f5ae6e23a53281e7eb4f1d5f2 Mon Sep 17 00:00:00 2001 From: Exter-N Date: Fri, 13 Feb 2026 02:54:32 +0100 Subject: [PATCH] Add DT material parameters to Advanced Dyes --- Glamourer/Configuration.cs | 1 + Glamourer/Designs/DesignEditor.cs | 28 +- .../Designs/History/DesignTransaction.cs | 19 +- Glamourer/Events/DesignChanged.cs | 3 + Glamourer/Gui/Materials/AdvancedDyePopup.cs | 254 ++++++++++++++--- Glamourer/Gui/Materials/ColorRowClipboard.cs | 4 + Glamourer/Gui/Materials/MaterialDrawer.cs | 143 ++++++++-- Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs | 31 +++ Glamourer/Interop/Material/MaterialManager.cs | 2 +- .../Interop/Material/MaterialValueManager.cs | 255 +++++++++++++++--- Glamourer/State/InternalStateEditor.cs | 2 +- Glamourer/State/StateEditor.cs | 5 +- 12 files changed, 626 insertions(+), 121 deletions(-) diff --git a/Glamourer/Configuration.cs b/Glamourer/Configuration.cs index 219e8cf..0ceb056 100644 --- a/Glamourer/Configuration.cs +++ b/Glamourer/Configuration.cs @@ -82,6 +82,7 @@ public class Configuration : IPluginConfiguration, ISavable public bool AllowDoubleClickToApply { get; set; } = false; public bool RespectManualOnAutomationUpdate { get; set; } = false; public bool PreventRandomRepeats { get; set; } = false; + public bool? AlwaysEditAsRoughness { get; set; } = null; public string PcpFolder { get; set; } = "PCP"; public string PcpColor { get; set; } = ""; diff --git a/Glamourer/Designs/DesignEditor.cs b/Glamourer/Designs/DesignEditor.cs index e5c0357..c6fbe86 100644 --- a/Glamourer/Designs/DesignEditor.cs +++ b/Glamourer/Designs/DesignEditor.cs @@ -256,7 +256,21 @@ public class DesignEditor( DesignChanged.Invoke(DesignChanged.Type.MaterialRevert, design, new MaterialRevertTransaction(index, !revert, revert)); } - public void ChangeMaterialValue(Design design, MaterialValueIndex index, ColorRow? row) + public void ChangeMaterialMode(Design design, MaterialValueIndex index, ColorRow.Mode mode) + { + var materials = design.GetMaterialDataRef(); + if (!materials.TryGetValue(index, out var oldValue)) + return; + + var oldMode = oldValue.Mode; + materials.AddOrUpdateValue(index, oldValue with { Mode = mode }); + Glamourer.Log.Debug($"Changed advanced dye value for {index} from {oldMode} to {mode} mode."); + design.LastEdit = DateTimeOffset.UtcNow; + SaveService.QueueSave(design); + DesignChanged.Invoke(DesignChanged.Type.MaterialMode, design, new MaterialModeTransaction(index, oldMode, mode)); + } + + public void ChangeMaterialValue(Design design, MaterialValueIndex index, ColorRow? row, ColorRow.Mode? mode = null) { var materials = design.GetMaterialDataRef(); if (materials.TryGetValue(index, out var oldValue)) @@ -268,9 +282,14 @@ public class DesignEditor( } else if (!row.Value.NearEqual(oldValue.Value)) { - materials.UpdateValue(index, new MaterialValueDesign(row.Value, oldValue.Enabled, oldValue.Revert), out _); + materials.UpdateValue(index, new MaterialValueDesign(row.Value, oldValue.Enabled, oldValue.Revert, mode ?? oldValue.Mode), out _); Glamourer.Log.Debug($"Updated advanced dye value for {index} to new value."); } + else if (mode.HasValue && mode.Value != oldValue.Mode) + { + ChangeMaterialMode(design, index, mode.Value); + return; + } else { return; @@ -280,7 +299,7 @@ public class DesignEditor( { if (!row.HasValue) return; - if (!materials.TryAddValue(index, new MaterialValueDesign(row.Value, true, false))) + if (!materials.TryAddValue(index, new MaterialValueDesign(row.Value, true, false, mode ?? ColorRow.Mode.Legacy))) return; Glamourer.Log.Debug($"Added new advanced dye value for {index}."); @@ -288,7 +307,8 @@ public class DesignEditor( design.LastEdit = DateTimeOffset.UtcNow; SaveService.DelaySave(design); - DesignChanged.Invoke(DesignChanged.Type.Material, design, new MaterialTransaction(index, oldValue.Value, row)); + DesignChanged.Invoke(DesignChanged.Type.Material, design, + new MaterialTransaction(index, oldValue.Value, row, mode.HasValue ? oldValue.Mode : null, mode)); } public void ChangeApplyMaterialValue(Design design, MaterialValueIndex index, bool value) diff --git a/Glamourer/Designs/History/DesignTransaction.cs b/Glamourer/Designs/History/DesignTransaction.cs index 65086db..157e71d 100644 --- a/Glamourer/Designs/History/DesignTransaction.cs +++ b/Glamourer/Designs/History/DesignTransaction.cs @@ -119,16 +119,18 @@ public readonly record struct ModUpdatedTransaction(Mod Mod, ModSettings Old, Mo } /// Only Designs. -public readonly record struct MaterialTransaction(MaterialValueIndex Index, ColorRow? Old, ColorRow? New) +public readonly record struct MaterialTransaction(MaterialValueIndex Index, ColorRow? Old, ColorRow? New, ColorRow.Mode? OldMode, ColorRow.Mode? NewMode) : ITransaction { public ITransaction? Merge(ITransaction older) - => older is MaterialTransaction other && Index == other.Index ? new MaterialTransaction(Index, other.Old, New) : null; + => older is MaterialTransaction other && Index == other.Index + ? new MaterialTransaction(Index, other.Old, New, other.OldMode ?? OldMode, NewMode ?? other.NewMode) + : null; public void Revert(IDesignEditor editor, object data) { if (editor is DesignManager e) - e.ChangeMaterialValue((Design)data, Index, Old); + e.ChangeMaterialValue((Design)data, Index, Old, OldMode); } } @@ -143,6 +145,17 @@ public readonly record struct MaterialRevertTransaction(MaterialValueIndex Index => ((DesignManager)editor).ChangeMaterialRevert((Design)data, Index, Old); } +/// Only Designs. +public readonly record struct MaterialModeTransaction(MaterialValueIndex Index, ColorRow.Mode Old, ColorRow.Mode New) + : ITransaction +{ + public ITransaction? Merge(ITransaction other) + => null; + + public void Revert(IDesignEditor editor, object data) + => ((DesignManager)editor).ChangeMaterialMode((Design)data, Index, Old); +} + /// Only Designs. public readonly record struct ApplicationTransaction(object Index, bool Old, bool New) : ITransaction diff --git a/Glamourer/Events/DesignChanged.cs b/Glamourer/Events/DesignChanged.cs index 04bb46a..b6e708e 100644 --- a/Glamourer/Events/DesignChanged.cs +++ b/Glamourer/Events/DesignChanged.cs @@ -87,6 +87,9 @@ public sealed class DesignChanged() /// An existing design had an advanced dye rows Revert state changed. MaterialRevert, + /// An existing design had an advanced dye rows mode changed. + MaterialMode, + /// An existing design had changed whether it always forces a redraw or not. ForceRedraw, diff --git a/Glamourer/Gui/Materials/AdvancedDyePopup.cs b/Glamourer/Gui/Materials/AdvancedDyePopup.cs index bdbaa16..74a41c4 100644 --- a/Glamourer/Gui/Materials/AdvancedDyePopup.cs +++ b/Glamourer/Gui/Materials/AdvancedDyePopup.cs @@ -31,8 +31,9 @@ public sealed unsafe class AdvancedDyePopup( private bool _anyChanged; private bool _forceFocus; - private const int RowsPerPage = 16; - private int _rowOffset; + private const int RowsPerPage = 16; + private int _rowOffset; + private bool _editSheen; private bool ShouldBeDrawn() { @@ -149,28 +150,51 @@ public sealed unsafe class AdvancedDyePopup( if ((tab.Success || select is TabItemFlags.SetSelected) && available) { _selectedMaterial = i; - DrawToggle(); + DrawToggles(); DrawTable(index, table); } } } - private void DrawToggle() + private void DrawToggles() { - var buttonWidth = new Vector2(Im.ContentRegion.Available.X / 2, 0); - using var font = Im.Font.PushMono(); - using var hoverColor = ImGuiColor.ButtonHovered.Push(Im.Style[ImGuiColor.TabHovered]); + var layerButtonWidth = _mode is ColorRow.Mode.Dawntrail ? Im.Font.Mono.GetCharacterAdvance(' ') * 5 + Im.Style.FramePadding.X * 2 : 0; + var buttonWidth = + (Im.ContentRegion.Available.X - (_mode is ColorRow.Mode.Dawntrail ? layerButtonWidth * 2 + Im.Style.ItemSpacing.X : 0)) / 2; + var buttonSize = new Vector2(buttonWidth, 0); + using var font = Im.Font.PushMono(); + using var hoverColor = ImGuiColor.ButtonHovered.Push(Im.Style[ImGuiColor.TabHovered]); hoverColor.Push(ImGuiColor.Button, Im.Style[_rowOffset is 0 ? ImGuiColor.TabSelected : ImGuiColor.Tab]); - if (ImEx.ButtonCorners("Row Pairs 1-8 "u8, buttonWidth, ButtonFlags.MouseButtonLeft, Corners.Left)) + if (ImEx.ButtonCorners("Row Pairs 1-8 "u8, buttonSize, ButtonFlags.MouseButtonLeft, Corners.Left)) _rowOffset = 0; hoverColor.Pop(); Im.Line.NoSpacing(); hoverColor.Push(ImGuiColor.Button, Im.Style[_rowOffset is RowsPerPage ? ImGuiColor.TabSelected : ImGuiColor.Tab]); - if (ImEx.ButtonCorners("Row Pairs 9-16"u8, buttonWidth, ButtonFlags.MouseButtonLeft, Corners.Right)) + if (ImEx.ButtonCorners("Row Pairs 9-16"u8, buttonSize, ButtonFlags.MouseButtonLeft, Corners.Right)) _rowOffset = RowsPerPage; + hoverColor.Pop(); + + if (_mode is ColorRow.Mode.Dawntrail) + { + Im.Line.Same(); + + buttonSize = new Vector2(layerButtonWidth, 0); + + hoverColor.Push(ImGuiColor.Button, Im.Style[!_editSheen ? ImGuiColor.TabSelected : ImGuiColor.Tab]); + if (ImEx.ButtonCorners("Base"u8, buttonSize, ButtonFlags.MouseButtonLeft, Corners.Left)) + _editSheen = false; + hoverColor.Pop(); + + Im.Line.NoSpacing(); + + hoverColor.Push(ImGuiColor.Button, Im.Style[_editSheen ? ImGuiColor.TabSelected : ImGuiColor.Tab]); + if (ImEx.ButtonCorners("Sheen"u8, buttonSize, ButtonFlags.MouseButtonLeft, Corners.Right)) + _editSheen = true; + hoverColor.Pop(); + } } private void DrawContent(ReadOnlySpan> textures, @@ -323,7 +347,8 @@ public sealed unsafe class AdvancedDyePopup( Im.Line.Same(Im.Window.Size.X - 3 * Im.Style.FrameHeight - 2 * spacing - Im.Style.WindowPadding.X); if (ImEx.Icon.Button(LunaStyle.ToClipboardIcon, "Export this table to your clipboard."u8)) { - ColorRowClipboard.Table = table; + ColorRowClipboard.Table = table; + ColorRowClipboard.TableMode = _mode; CopyToClipboard(table); } @@ -333,7 +358,7 @@ public sealed unsafe class AdvancedDyePopup( for (var idx = 0; idx < ColorTable.NumRows; ++idx) { var row = newTable[idx]; - var internalRow = new ColorRow(row); + var internalRow = ColorRow.From(row, _mode); var slot = materialIndex.ToEquipSlot(); var weapon = slot is EquipSlot.MainHand or EquipSlot.OffHand ? _state.ModelData.Weapon(slot) @@ -354,7 +379,7 @@ public sealed unsafe class AdvancedDyePopup( var changed = _state.Materials.TryGetValue(index, out var value); if (!changed) { - var internalRow = new ColorRow(row); + var internalRow = ColorRow.From(row, _mode); var slot = index.ToEquipSlot(); var weapon = slot switch { @@ -369,7 +394,9 @@ public sealed unsafe class AdvancedDyePopup( else { _anyChanged = true; - value = new MaterialValueState(value.Game, value.Model, value.DrawData, StateSource.Manual); + value = new MaterialValueState(value.Game, + value.Model.IsPartial(_mode) ? value.Model.MergeOnto(ColorRow.From(row, _mode)) : value.Model, + value.DrawData, StateSource.Manual); } ImEx.Icon.Button(LunaStyle.OnHoverIcon, "Highlight the affected colors on the character."u8); @@ -385,45 +412,79 @@ public sealed unsafe class AdvancedDyePopup( } Im.Line.Same(0, Im.Style.ItemSpacing.X * 2); - var applied = ImEx.ColorPickerButton("##diffuse"u8, "Change the diffuse value for this row."u8, value.Model.Diffuse, + var applied = ImEx.ColorPickerButton("##diffuse"u8, "Change the diffuse color for this row."u8, value.Model.Diffuse, out value.Model.Diffuse, 'D'); var spacing = Im.Style.ItemInnerSpacing; Im.Line.Same(0, spacing.X); - applied |= ImEx.ColorPickerButton("##specular"u8, "Change the specular value for this row."u8, value.Model.Specular, + applied |= ImEx.ColorPickerButton("##specular"u8, "Change the specular color for this row."u8, value.Model.Specular, out value.Model.Specular, 'S'); Im.Line.Same(0, spacing.X); - applied |= ImEx.ColorPickerButton("##emissive"u8, "Change the emissive value for this row."u8, value.Model.Emissive, + applied |= ImEx.ColorPickerButton("##emissive"u8, "Change the emissive color for this row."u8, value.Model.Emissive, out value.Model.Emissive, 'E'); - + Im.Line.Same(0, spacing.X); - if (_mode is not ColorRow.Mode.Dawntrail) + + if (_mode is ColorRow.Mode.Dawntrail && _editSheen) { - Im.Item.SetNextWidthScaled(100); - applied |= DragGloss(ref value.Model.GlossStrength); - Im.Tooltip.OnHover("Change the gloss strength for this row."u8); + // The other layout has 2 items of width 100*sc and one spacing, for a total of 200*sc + 1*sp. + // This layout has 3 items and two spacings: 3*w + 2*sp = 200*sc + 1*sp. + var allItemsWidth = 200 * Im.Style.GlobalScale - spacing.X; + var itemWidth = MathF.Floor(allItemsWidth / 3); + + Im.Item.SetNextWidth(allItemsWidth - itemWidth * 2); + applied |= DragSheen(ref value.Model.Sheen, false); + Im.Tooltip.OnHover("Change the sheen strength for this row."u8); + + Im.Line.Same(0, spacing.X); + + Im.Item.SetNextWidth(itemWidth); + applied |= DragSheenTint(ref value.Model.SheenTint, false); + Im.Tooltip.OnHover("Change the sheen tint for this row."u8); + + Im.Line.Same(0, spacing.X); + + Im.Item.SetNextWidth(itemWidth); + applied |= DragSheenRoughness(ref value.Model.SheenAperture, false); + Im.Tooltip.OnHover("Change the sheen roughness for this row."u8); } else - { - Im.Dummy(new Vector2(100 * Im.Style.GlobalScale, 0)); - } - - Im.Line.Same(0, spacing.X); - if (_mode is not ColorRow.Mode.Dawntrail) { Im.Item.SetNextWidthScaled(100); - applied |= DragSpecularStrength(ref value.Model.SpecularStrength); - Im.Tooltip.OnHover("Change the specular strength for this row."u8); - } - else - { - Im.Dummy(new Vector2(100 * Im.Style.GlobalScale, 0)); + var editAsRoughness = config.AlwaysEditAsRoughness ?? _mode is ColorRow.Mode.Dawntrail; + applied |= (_mode, editAsRoughness) switch + { + (ColorRow.Mode.Legacy, false) => DragGloss(ref value.Model.GlossStrength, false), + (ColorRow.Mode.Legacy, true) => DragGlossAsRoughness(ref value.Model.GlossStrength, false), + (ColorRow.Mode.Dawntrail, false) => DragRoughnessAsGloss(ref value.Model.Roughness, false), + (ColorRow.Mode.Dawntrail, true) => DragRoughness(ref value.Model.Roughness, false), + _ => throw new NotImplementedException(), + }; + Im.Tooltip.OnHover(editAsRoughness ? "Change the roughness for this row."u8 : "Change the gloss strength for this row."u8); + + Im.Line.Same(0, spacing.X); + if (_mode is not ColorRow.Mode.Dawntrail) + { + Im.Item.SetNextWidthScaled(100); + applied |= DragSpecularStrength(ref value.Model.SpecularStrength, false); + Im.Tooltip.OnHover("Change the specular strength for this row."u8); + } + else + { + Im.Item.SetNextWidthScaled(100); + applied |= DragMetalness(ref value.Model.Metalness, false); + Im.Tooltip.OnHover("Change the metalness for this row."u8); + } } Im.Line.Same(0, spacing.X); if (ImEx.Icon.Button(LunaStyle.ToClipboardIcon, "Export this row to your clipboard."u8)) - ColorRowClipboard.Row = value.Model; + { + ColorRowClipboard.Row = value.Model; + ColorRowClipboard.RowMode = _mode; + } + Im.Line.Same(0, spacing.X); if (ImEx.Icon.Button(LunaStyle.FromClipboardIcon, "Import an exported row from your clipboard onto this row."u8, !ColorRowClipboard.IsSet)) @@ -440,12 +501,12 @@ public sealed unsafe class AdvancedDyePopup( stateManager.ChangeMaterialValue(_state, index, value, ApplySettings.Manual); } - public static bool DragGloss(ref float value) + public static bool DragGloss(ref float value, bool canUnset) { - var tmp = value; + var tmp = float.IsNaN(value) ? ColorRow.DefaultGlossStrength : value; var minValue = Im.Io.KeyControl ? 0f : (float)Half.Epsilon; - if (!Im.Drag("##Gloss"u8, ref tmp, "%.1f G"u8, 0.001f, minValue, Math.Max(0.01f, 0.005f * value), SliderFlags.AlwaysClamp)) - return false; + if (!Im.Drag("##Gloss"u8, ref tmp, float.IsNaN(value) ? "\u2014 G"u8 : "%.1f G"u8, 0.001f, minValue, Math.Max(0.01f, 0.005f * value), SliderFlags.AlwaysClamp)) + return UnsetBehavior(ref value, canUnset); var tmp2 = Math.Clamp(tmp, minValue, (float)Half.MaxValue); if (tmp2 == value) @@ -455,12 +516,27 @@ public sealed unsafe class AdvancedDyePopup( return true; } - public static bool DragSpecularStrength(ref float value) + public static bool DragGlossAsRoughness(ref float value, bool canUnset) { - var tmp = value * 100f; - if (!Im.Drag("##SpecularStrength"u8, ref tmp, "%.0f%% SS"u8, 0f, (float)Half.MaxValue * 100f, 0.05f, SliderFlags.AlwaysClamp)) + var roughness = ColorTableRow.RoughnessFromShininess(float.IsNaN(value) ? ColorRow.DefaultGlossStrength : value); + var tmp = roughness * 100f; + if (!Im.Drag("##Gloss"u8, ref tmp, float.IsNaN(value) ? "\u2014 Rg"u8 : "%.0f%% Rg"u8, 0f, 100f, 0.25f, SliderFlags.AlwaysClamp)) + return UnsetBehavior(ref value, canUnset); + + var tmp2 = Math.Clamp(tmp, 0f, 100f) / 100f; + if (tmp2 == roughness) return false; + value = ColorTableRow.ShininessFromRoughness(tmp2); + return true; + } + + public static bool DragSpecularStrength(ref float value, bool canUnset) + { + var tmp = (float.IsNaN(value) ? ColorRow.DefaultSpecularStrength : value) * 100f; + if (!Im.Drag("##SpecularStrength"u8, ref tmp, float.IsNaN(value) ? "\u2014 SS"u8 : "%.0f%% SS"u8, 0f, (float)Half.MaxValue * 100f, 0.05f, SliderFlags.AlwaysClamp)) + return UnsetBehavior(ref value, canUnset); + var tmp2 = Math.Clamp(tmp, 0f, (float)Half.MaxValue * 100f) / 100f; if (tmp2 == value) return false; @@ -469,6 +545,102 @@ public sealed unsafe class AdvancedDyePopup( return true; } + public static bool DragRoughness(ref float value, bool canUnset) + { + var tmp = (float.IsNaN(value) ? ColorRow.DefaultRoughness : value) * 100f; + if (!Im.Drag("##Roughness"u8, ref tmp, float.IsNaN(value) ? "\u2014 Rg"u8 : "%.0f%% Rg"u8, 0f, 100f, 0.25f, SliderFlags.AlwaysClamp)) + return UnsetBehavior(ref value, canUnset); + + var tmp2 = Math.Clamp(tmp, 0f, 100f) / 100f; + if (tmp2 == value) + return false; + + value = tmp2; + return true; + } + + public static bool DragRoughnessAsGloss(ref float value, bool canUnset) + { + var gloss = ColorTableRow.ShininessFromRoughness(float.IsNaN(value) ? ColorRow.DefaultRoughness : value); + var tmp = gloss; + if (!Im.Drag("##Roughness"u8, ref tmp, float.IsNaN(value) ? "\u2014 G"u8 : "%.1f G"u8, 0.001f, (float)Half.Epsilon, Math.Max(0.01f, 0.005f * gloss), SliderFlags.AlwaysClamp)) + return UnsetBehavior(ref value, canUnset); + + var tmp2 = Math.Clamp(tmp, (float)Half.Epsilon, (float)Half.MaxValue); + if (tmp2 == gloss) + return false; + + value = ColorTableRow.RoughnessFromShininess(tmp2); + return true; + } + + public static bool DragMetalness(ref float value, bool canUnset) + { + var tmp = (float.IsNaN(value) ? ColorRow.DefaultMetalness : value) * 100f; + if (!Im.Drag("##Metalness"u8, ref tmp, float.IsNaN(value) ? "\u2014 Mt"u8 : "%.0f%% Mt"u8, 0f, 100f, 0.25f, SliderFlags.AlwaysClamp)) + return UnsetBehavior(ref value, canUnset); + + var tmp2 = Math.Clamp(tmp, 0f, 100f) / 100f; + if (tmp2 == value) + return false; + + value = tmp2; + return true; + } + + public static bool DragSheen(ref float value, bool canUnset) + { + var tmp = (float.IsNaN(value) ? ColorRow.DefaultSheen : value) * 100f; + if (!Im.Drag("##Sheen"u8, ref tmp, float.IsNaN(value) ? "\u2014 Sh"u8 : "%.0f%% Sh"u8, 0f, 100f * (float)Half.MaxValue, 0.25f, SliderFlags.AlwaysClamp)) + return UnsetBehavior(ref value, canUnset); + + var tmp2 = Math.Clamp(tmp, 0f, 100f * (float)Half.MaxValue) / 100f; + if (tmp2 == value) + return false; + + value = tmp2; + return true; + } + + public static bool DragSheenTint(ref float value, bool canUnset) + { + var tmp = (float.IsNaN(value) ? ColorRow.DefaultSheenTint : value) * 100f; + if (!Im.Drag("##SheenTint"u8, ref tmp, float.IsNaN(value) ? "\u2014 ST"u8 : "%.0f%% ST"u8, -100f * (float)Half.MaxValue, 100f * (float)Half.MaxValue, 0.25f, + SliderFlags.AlwaysClamp)) + return UnsetBehavior(ref value, canUnset); + + var tmp2 = Math.Clamp(tmp, -100f * (float)Half.MaxValue, 100f * (float)Half.MaxValue) / 100f; + if (tmp2 == value) + return false; + + value = tmp2; + return true; + } + + public static bool DragSheenRoughness(ref float value, bool canUnset) + { + var tmp = 100f / (float.IsNaN(value) ? ColorRow.DefaultSheenAperture : value); + if (!Im.Drag("##SheenAperture"u8, ref tmp, float.IsNaN(value) ? "\u2014 SR"u8 : "%.0f%% SR"u8, 100f / (float)Half.MaxValue, 100f / (float)Half.Epsilon, 0.25f, + SliderFlags.AlwaysClamp)) + return UnsetBehavior(ref value, canUnset); + + var tmp2 = Math.Clamp(100f / tmp, (float)Half.Epsilon, (float)Half.MaxValue); + if (tmp2 == value) + return false; + + value = tmp2; + return true; + } + + private static bool UnsetBehavior(ref float value, bool canUnset) + { + if (!(canUnset && Im.Item.RightClicked() && Im.Io.KeyControl)) + return false; + + value = float.NaN; + return true; + } + private LabelStruct _label = new(); private struct LabelStruct diff --git a/Glamourer/Gui/Materials/ColorRowClipboard.cs b/Glamourer/Gui/Materials/ColorRowClipboard.cs index 329211b..a4cb674 100644 --- a/Glamourer/Gui/Materials/ColorRowClipboard.cs +++ b/Glamourer/Gui/Materials/ColorRowClipboard.cs @@ -18,6 +18,8 @@ public static class ColorRowClipboard field = value; } } + + public static ColorRow.Mode TableMode { get; set; } public static ColorRow Row { @@ -28,4 +30,6 @@ public static class ColorRowClipboard field = value; } } + + public static ColorRow.Mode RowMode { get; set; } } diff --git a/Glamourer/Gui/Materials/MaterialDrawer.cs b/Glamourer/Gui/Materials/MaterialDrawer.cs index 766ff20..9187649 100644 --- a/Glamourer/Gui/Materials/MaterialDrawer.cs +++ b/Glamourer/Gui/Materials/MaterialDrawer.cs @@ -9,8 +9,9 @@ namespace Glamourer.Gui.Materials; public class MaterialDrawer(DesignManager designManager, Configuration config) : IService { - public const float GlossWidth = 100; - public const float SpecularStrengthWidth = 125; + public const float SliderWidth = 90; + public const float ModeWidth = 45; + public const float SheenSliderWidth = 75; // Should satisfy 2*Slider + Mode = 3*SheenSlider private int _newMaterialIdx; private int _newRowIdx; @@ -25,17 +26,17 @@ public class MaterialDrawer(DesignManager designManager, Configuration config) : _spacing = Im.Style.ItemInnerSpacing.X; _buttonSize = new Vector2(Im.Style.FrameHeight); var colorWidth = 4 * _buttonSize.X - + (GlossWidth + SpecularStrengthWidth) * Im.Style.GlobalScale - + 6 * _spacing + + (SliderWidth * 2 + ModeWidth) * Im.Style.GlobalScale + + 7 * _spacing + Im.Font.CalculateSize("Revert"u8).X; DrawMultiButtons(design); Im.Dummy(0); Im.Separator(); Im.Dummy(0); - if (available > 1.95 * colorWidth) + if (available > 2.6f * colorWidth) DrawSingleRow(design); else - DrawTwoRow(design); + DrawMultipleRow(design); DrawNew(design); } @@ -75,7 +76,7 @@ public class MaterialDrawer(DesignManager designManager, Configuration config) : private void DrawName(MaterialValueIndex index) { using var style = ImStyleDouble.ButtonTextAlign.Push(new Vector2(0.05f, 0.5f)); - ImEx.TextFramed($"{index}", new Vector2((GlossWidth + SpecularStrengthWidth) * Im.Style.GlobalScale + _spacing, 0), + ImEx.TextFramed($"{index}", new Vector2((SliderWidth * 2 + ModeWidth) * Im.Style.GlobalScale + _spacing * 2, 0), borderColor: ImGuiColor.Text.Get()); } @@ -91,20 +92,21 @@ public class MaterialDrawer(DesignManager designManager, Configuration config) : Im.Line.Same(0, _spacing); DeleteButton(design, key, ref i); Im.Line.Same(0, _spacing); - CopyButton(value.Value); + CopyButton(value.Value, value.Mode); Im.Line.Same(0, _spacing); PasteButton(design, key); Im.Line.Same(0, _spacing); using var disabled = Im.Disabled(design.WriteProtected()); EnabledToggle(design, key, value.Enabled); Im.Line.Same(0, _spacing); - DrawRow(design, key, value.Value, value.Revert); + DrawRow(design, key, value.Value, value.Revert, value.Mode); + DrawRowExtra(design, key, value.Value, value.Revert, value.Mode, true); Im.Line.Same(0, _spacing); RevertToggle(design, key, value.Revert); } } - private void DrawTwoRow(Design design) + private void DrawMultipleRow(Design design) { for (var i = 0; i < design.Materials.Count; ++i) { @@ -116,16 +118,18 @@ public class MaterialDrawer(DesignManager designManager, Configuration config) : Im.Line.Same(0, _spacing); DeleteButton(design, key, ref i); Im.Line.Same(0, _spacing); - CopyButton(value.Value); + CopyButton(value.Value, value.Mode); Im.Line.Same(0, _spacing); PasteButton(design, key); Im.Line.Same(0, _spacing); + using var disabled = Im.Disabled(design.WriteProtected()); EnabledToggle(design, key, value.Enabled); - DrawRow(design, key, value.Value, value.Revert); + DrawRow(design, key, value.Value, value.Revert, value.Mode); Im.Line.Same(0, _spacing); RevertToggle(design, key, value.Revert); + DrawRowExtra(design, key, value.Value, value.Revert, value.Mode, false); Im.Separator(); } } @@ -142,17 +146,20 @@ public class MaterialDrawer(DesignManager designManager, Configuration config) : --idx; } - private void CopyButton(in ColorRow row) + private void CopyButton(in ColorRow row, ColorRow.Mode mode) { if (ImEx.Icon.Button(LunaStyle.ToClipboardIcon, "Export this row to your clipboard."u8)) - ColorRowClipboard.Row = row; + { + ColorRowClipboard.Row = row; + ColorRowClipboard.RowMode = mode; + } } private void PasteButton(Design design, MaterialValueIndex index) { if (ImEx.Icon.Button(LunaStyle.FromClipboardIcon, "Import an exported row from your clipboard onto this row."u8, !ColorRowClipboard.IsSet || design.WriteProtected())) - designManager.ChangeMaterialValue(design, index, ColorRowClipboard.Row); + designManager.ChangeMaterialValue(design, index, ColorRowClipboard.Row, ColorRowClipboard.RowMode); } private void EnabledToggle(Design design, MaterialValueIndex index, bool enabled) @@ -169,6 +176,39 @@ public class MaterialDrawer(DesignManager designManager, Configuration config) : "If this is checked, Glamourer will try to revert the advanced dye row to its game state instead of applying a specific row."u8); } + private void ModeToggle(Design design, MaterialValueIndex index, ColorRow.Mode mode) + { + if (Im.Button(ToCallsignString(mode), new Vector2(ModeWidth * Im.Style.GlobalScale, 0))) + designManager.ChangeMaterialMode(design, index, GetNextMode(mode)); + Im.Tooltip.OnHover(ToTooltipString(mode)); + + return; + + static ReadOnlySpan ToCallsignString(ColorRow.Mode mode) + => mode switch + { + ColorRow.Mode.Legacy => "Lgc###mode"u8, + ColorRow.Mode.Dawntrail => "DT###mode"u8, + _ => throw new NotImplementedException(), + }; + + static ColorRow.Mode GetNextMode(ColorRow.Mode mode) + => mode switch + { + ColorRow.Mode.Legacy => ColorRow.Mode.Dawntrail, + ColorRow.Mode.Dawntrail => ColorRow.Mode.Legacy, + _ => throw new NotImplementedException(), + }; + + static ReadOnlySpan ToTooltipString(ColorRow.Mode mode) + => mode switch + { + ColorRow.Mode.Legacy => "This color row currently contains Legacy material parameters.\nClick this button to switch it to Dawntrail parameters."u8, + ColorRow.Mode.Dawntrail => "This color row currently contains Dawntrail material parameters.\nClick this button to switch it to Legacy parameters."u8, + _ => throw new NotImplementedException(), + }; + } + public sealed class MaterialSlotCombo; private void DrawSlotCombo() @@ -245,23 +285,76 @@ public class MaterialDrawer(DesignManager designManager, Configuration config) : Im.Tooltip.OnHover("Drag this to the left or right to change its value."u8); } - private void DrawRow(Design design, MaterialValueIndex index, in ColorRow row, bool disabled) + private void DrawRow(Design design, MaterialValueIndex index, in ColorRow row, bool disabled, ColorRow.Mode mode) { var tmp = row; using var _ = Im.Disabled(disabled); - var applied = ImEx.ColorPickerButton("##diffuse"u8, "Change the diffuse value for this row."u8, row.Diffuse, out tmp.Diffuse, 'D'); + var applied = ImEx.ColorPickerButton("##diffuse"u8, "Change the diffuse color for this row."u8, row.Diffuse, out tmp.Diffuse, 'D'); Im.Line.SameInner(); - applied |= ImEx.ColorPickerButton("##specular"u8, "Change the specular value for this row."u8, row.Specular, out tmp.Specular, 'S'); + applied |= ImEx.ColorPickerButton("##specular"u8, "Change the specular color for this row."u8, row.Specular, out tmp.Specular, 'S'); Im.Line.SameInner(); - applied |= ImEx.ColorPickerButton("##emissive"u8, "Change the emissive value for this row."u8, row.Emissive, out tmp.Emissive, 'E'); + applied |= ImEx.ColorPickerButton("##emissive"u8, "Change the emissive color for this row."u8, row.Emissive, out tmp.Emissive, 'E'); Im.Line.SameInner(); - Im.Item.SetNextWidth(GlossWidth * Im.Style.GlobalScale); - applied |= AdvancedDyePopup.DragGloss(ref tmp.GlossStrength); - Im.Tooltip.OnHover("Change the gloss strength for this row."u8); + ModeToggle(design, index, mode); Im.Line.SameInner(); - Im.Item.SetNextWidth(SpecularStrengthWidth * Im.Style.GlobalScale); - applied |= AdvancedDyePopup.DragSpecularStrength(ref tmp.SpecularStrength); - Im.Tooltip.OnHover("Change the specular strength for this row."u8); + Im.Item.SetNextWidth(SliderWidth * Im.Style.GlobalScale); + var editAsRoughness = config.AlwaysEditAsRoughness ?? mode is ColorRow.Mode.Dawntrail; + applied |= (mode, editAsRoughness) switch + { + (ColorRow.Mode.Legacy, false) => AdvancedDyePopup.DragGloss(ref tmp.GlossStrength, true), + (ColorRow.Mode.Legacy, true) => AdvancedDyePopup.DragGlossAsRoughness(ref tmp.GlossStrength, true), + (ColorRow.Mode.Dawntrail, false) => AdvancedDyePopup.DragRoughnessAsGloss(ref tmp.Roughness, true), + (ColorRow.Mode.Dawntrail, true) => AdvancedDyePopup.DragRoughness(ref tmp.Roughness, true), + _ => throw new NotImplementedException(), + }; + Im.Tooltip.OnHover(editAsRoughness + ? "Change the roughness for this row.\nControl and Right-Click to unset."u8 + : "Change the gloss strength for this row.\nControl and Right-Click to unset."u8); + if (mode is ColorRow.Mode.Dawntrail) + { + Im.Line.SameInner(); + Im.Item.SetNextWidth(SliderWidth * Im.Style.GlobalScale); + applied |= AdvancedDyePopup.DragMetalness(ref tmp.Metalness, true); + Im.Tooltip.OnHover("Change the metalness for this row.\nControl and Right-Click to unset."u8); + } + else + { + Im.Line.SameInner(); + Im.Item.SetNextWidth(SliderWidth * Im.Style.GlobalScale); + applied |= AdvancedDyePopup.DragSpecularStrength(ref tmp.SpecularStrength, true); + Im.Tooltip.OnHover("Change the specular strength for this row.\nControl and Right-Click to unset."u8); + } + + if (applied) + designManager.ChangeMaterialValue(design, index, tmp); + } + + private void DrawRowExtra(Design design, MaterialValueIndex index, in ColorRow row, bool disabled, ColorRow.Mode mode, bool compact) + { + if (mode is not ColorRow.Mode.Dawntrail) + return; + + var tmp = row; + using var _ = Im.Disabled(disabled); + + if (!compact) + Im.Dummy(new Vector2(_buttonSize.X * 3 + _spacing * 2, _buttonSize.Y)); + + Im.Line.SameInner(); + Im.Item.SetNextWidth(SheenSliderWidth * Im.Style.GlobalScale); + var applied = AdvancedDyePopup.DragSheen(ref tmp.Sheen, true); + Im.Tooltip.OnHover("Change the sheen strength for this row.\nControl and Right-Click to unset."u8); + + Im.Line.SameInner(); + Im.Item.SetNextWidth(SheenSliderWidth * Im.Style.GlobalScale); + applied |= AdvancedDyePopup.DragSheenTint(ref tmp.SheenTint, true); + Im.Tooltip.OnHover("Change the sheen tint for this row.\nControl and Right-Click to unset."u8); + + Im.Line.SameInner(); + Im.Item.SetNextWidth(SheenSliderWidth * Im.Style.GlobalScale); + applied |= AdvancedDyePopup.DragSheenRoughness(ref tmp.SheenAperture, true); + Im.Tooltip.OnHover("Change the sheen roughness for this row.\nControl and Right-Click to unset."u8); + if (applied) designManager.ChangeMaterialValue(design, index, tmp); } diff --git a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs index de30188..249f3bc 100644 --- a/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs +++ b/Glamourer/Gui/Tabs/SettingsTab/SettingsTab.cs @@ -235,6 +235,7 @@ public sealed class SettingsTab( Checkbox("Smaller Equip Display"u8, "Use single-line display without icons and small dye buttons instead of double-line display."u8, config.SmallEquip, v => config.SmallEquip = v); DrawHeightUnitSettings(); + DrawRoughnessSettings(); Checkbox("Show Application Checkboxes"u8, "Show the application checkboxes in the Customization and Equipment panels of the design tab, instead of only showing them under Application Rules."u8, !config.HideApplyCheckmarks, v => config.HideApplyCheckmarks = !v); @@ -502,4 +503,34 @@ public sealed class SettingsTab( LunaStyle.DrawAlignedHelpMarkerLabel("Character Height Display Type"u8, "Select how to display the height of characters in real-world units, if at all."u8); } + + private void DrawRoughnessSettings() + { + Im.Item.SetNextWidthScaled(300); + using (var combo = Im.Combo.Begin("##alwaysEditAsRoughness"u8, ToRoughnessSettingString(config.AlwaysEditAsRoughness))) + { + if (combo) + foreach (var type in (IEnumerable)[null, true, false,]) + { + if (Im.Selectable(ToRoughnessSettingString(type), config.AlwaysEditAsRoughness == type)) + { + config.AlwaysEditAsRoughness = type; + config.Save(); + } + } + } + + LunaStyle.DrawAlignedHelpMarkerLabel("Gloss Strength and Roughness Display Type"u8, + "Select how to display and edit Gloss Strength and Roughness values.\nThe conversion formula used is an approximation and does not account for all the subtleties of legacy vs PBR shaders."u8); + + return; + + static ReadOnlySpan ToRoughnessSettingString(bool? alwaysEditAsRoughness) + => alwaysEditAsRoughness switch + { + null => "As-Is"u8, + true => "Always Roughness"u8, + false => "Always Gloss Strength"u8, + }; + } } diff --git a/Glamourer/Interop/Material/MaterialManager.cs b/Glamourer/Interop/Material/MaterialManager.cs index 3b99e2c..8cc7bdd 100644 --- a/Glamourer/Interop/Material/MaterialManager.cs +++ b/Glamourer/Interop/Material/MaterialManager.cs @@ -80,7 +80,7 @@ public sealed unsafe class MaterialManager : IRequiredService, IDisposable var idx = MaterialValueIndex.FromKey(values[i].Key); var materialValue = values[i].Value; ref var row = ref colorTable[idx.RowIndex]; - var newGame = new ColorRow(row); + var newGame = ColorRow.From(row, mode); if (materialValue.EqualGame(newGame, drawData)) materialValue.Model.Apply(ref row, mode); else diff --git a/Glamourer/Interop/Material/MaterialValueManager.cs b/Glamourer/Interop/Material/MaterialValueManager.cs index 01cb479..f490a60 100644 --- a/Glamourer/Interop/Material/MaterialValueManager.cs +++ b/Glamourer/Interop/Material/MaterialValueManager.cs @@ -11,7 +11,7 @@ using Penumbra.GameData.Structs; namespace Glamourer.Interop.Material; /// Values are not squared. -public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, float specularStrength, float glossStrength) +public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, float specularStrength, float glossStrength, float roughness, float metalness, float sheen, float sheenTint, float sheenAperture) { public enum Mode { @@ -19,26 +19,50 @@ public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, floa Dawntrail, } - public static readonly ColorRow Empty = new(Vector3.Zero, Vector3.Zero, Vector3.Zero, 1f, 1f); + public const float DefaultSpecularStrength = 1f; + public const float DefaultGlossStrength = 20f; + public const float DefaultRoughness = 0.5f; + public const float DefaultMetalness = 0f; + public const float DefaultSheen = 0.1f; + public const float DefaultSheenTint = 0.2f; + public const float DefaultSheenAperture = 5f; + + public static readonly ColorRow Empty = new(Vector3.Zero, Vector3.Zero, Vector3.Zero, float.NaN, float.NaN, float.NaN, float.NaN, float.NaN, + float.NaN, float.NaN); public Vector3 Diffuse = diffuse; public Vector3 Specular = specular; public Vector3 Emissive = emissive; public float SpecularStrength = specularStrength; public float GlossStrength = glossStrength; + public float Roughness = roughness; + public float Metalness = metalness; + public float Sheen = sheen; + public float SheenTint = sheenTint; + public float SheenAperture = sheenAperture; - public ColorRow(in ColorTableRow row) - : this(Root((Vector3)row.DiffuseColor), Root((Vector3)row.SpecularColor), Root((Vector3)row.EmissiveColor), - (float)row.LegacySpecularStrength(), - (float)row.LegacyGloss()) - { } + public static ColorRow From(in ColorTableRow row, Mode mode) + => mode switch + { + Mode.Legacy => new(Root((Vector3)row.DiffuseColor), Root((Vector3)row.SpecularColor), Root((Vector3)row.EmissiveColor), + (float)row.LegacySpecularStrength(), (float)row.LegacyGloss(), float.NaN, float.NaN, float.NaN, float.NaN, float.NaN), + Mode.Dawntrail => new(Root((Vector3)row.DiffuseColor), Root((Vector3)row.SpecularColor), Root((Vector3)row.EmissiveColor), + float.NaN, float.NaN, (float)row.DawntrailRoughness(), (float)row.DawntrailMetalness(), (float)row.DawntrailSheen(), + (float)row.DawntrailSheenTint(), (float)row.DawntrailSheenAperture()), + _ => throw new NotImplementedException(), + }; - public readonly bool NearEqual(in ColorRow rhs) + public readonly bool NearEqual(in ColorRow rhs, bool skipEmpty = false) => Diffuse.NearEqual(rhs.Diffuse) && Specular.NearEqual(rhs.Specular) && Emissive.NearEqual(rhs.Emissive) - && SpecularStrength.NearEqual(rhs.SpecularStrength) - && GlossStrength.NearEqual(rhs.GlossStrength); + && (float.IsNaN(SpecularStrength) ? skipEmpty || float.IsNaN(rhs.SpecularStrength) : SpecularStrength.NearEqual(rhs.SpecularStrength)) + && (float.IsNaN(GlossStrength) ? skipEmpty || float.IsNaN(rhs.GlossStrength) : GlossStrength.NearEqual(rhs.GlossStrength)) + && (float.IsNaN(Roughness) ? skipEmpty || float.IsNaN(rhs.Roughness) : Roughness.NearEqual(rhs.Roughness)) + && (float.IsNaN(Metalness) ? skipEmpty || float.IsNaN(rhs.Metalness) : Metalness.NearEqual(rhs.Metalness)) + && (float.IsNaN(Sheen) ? skipEmpty || float.IsNaN(rhs.Sheen) : Sheen.NearEqual(rhs.Sheen)) + && (float.IsNaN(SheenAperture) ? skipEmpty || float.IsNaN(rhs.SheenAperture) : SheenAperture.NearEqual(rhs.SheenAperture)) + && (float.IsNaN(SheenTint) ? skipEmpty || float.IsNaN(rhs.SheenTint) : SheenTint.NearEqual(rhs.SheenTint)); private static Vector3 Square(Vector3 value) => new(Square(value.X), Square(value.Y), Square(value.Z)); @@ -76,23 +100,85 @@ public struct ColorRow(Vector3 diffuse, Vector3 specular, Vector3 emissive, floa ret = true; } - if (mode is Mode.Legacy) + switch (mode) { - if (!((float)row.LegacySpecularStrength()).NearEqual(SpecularStrength)) - { - row.LegacySpecularStrengthWrite() = (Half)SpecularStrength; - ret = true; - } + case Mode.Legacy: + if (!float.IsNaN(SpecularStrength) && !((float)row.LegacySpecularStrength()).NearEqual(SpecularStrength)) + { + row.LegacySpecularStrengthWrite() = (Half)SpecularStrength; + ret = true; + } - if (!((float)row.LegacyGloss()).NearEqual(GlossStrength)) - { - row.LegacyGlossWrite() = (Half)GlossStrength; - ret = true; - } + if (!float.IsNaN(GlossStrength) && !((float)row.LegacyGloss()).NearEqual(GlossStrength)) + { + row.LegacyGlossWrite() = (Half)GlossStrength; + ret = true; + } + + break; + case Mode.Dawntrail: + if (!float.IsNaN(Roughness) && !((float)row.DawntrailRoughness()).NearEqual(Roughness)) + { + row.DawntrailRoughnessWrite() = (Half)Roughness; + ret = true; + } + + if (!float.IsNaN(Metalness) && !((float)row.DawntrailMetalness()).NearEqual(Metalness)) + { + row.DawntrailMetalnessWrite() = (Half)Metalness; + ret = true; + } + + if (!float.IsNaN(Sheen) && !((float)row.DawntrailSheen()).NearEqual(Sheen)) + { + row.DawntrailSheenWrite() = (Half)Sheen; + ret = true; + } + + if (!float.IsNaN(SheenAperture) && !((float)row.DawntrailSheenAperture()).NearEqual(SheenAperture)) + { + row.DawntrailSheenApertureWrite() = (Half)SheenAperture; + ret = true; + } + + if (!float.IsNaN(SheenTint) && !((float)row.DawntrailSheenTint()).NearEqual(SheenTint)) + { + row.DawntrailSheenTintWrite() = (Half)SheenTint; + ret = true; + } + + break; + default: throw new NotImplementedException(); } return ret; } + + public readonly ColorRow MergeOnto(ColorRow previous) + => new(Diffuse, Specular, Emissive, float.IsNaN(SpecularStrength) ? previous.SpecularStrength : SpecularStrength, + float.IsNaN(GlossStrength) ? previous.GlossStrength : GlossStrength, float.IsNaN(Roughness) ? previous.Roughness : Roughness, + float.IsNaN(Metalness) ? previous.Metalness : Metalness, float.IsNaN(Sheen) ? previous.Sheen : Sheen, + float.IsNaN(SheenTint) ? previous.SheenTint : SheenTint, float.IsNaN(SheenAperture) ? previous.SheenAperture : SheenAperture); + + public readonly bool IsPartial(Mode mode) + => mode switch + { + Mode.Legacy => float.IsNaN(SpecularStrength) || float.IsNaN(GlossStrength), + Mode.Dawntrail => float.IsNaN(Roughness) + || float.IsNaN(Metalness) + || float.IsNaN(Sheen) + || float.IsNaN(SheenTint) + || float.IsNaN(SheenAperture), + _ => throw new NotImplementedException(), + }; + + public readonly Mode GuessMode() + => float.IsNaN(Roughness) && float.IsNaN(Metalness) && float.IsNaN(Sheen) && float.IsNaN(SheenTint) && float.IsNaN(SheenAperture) + ? Mode.Legacy + : Mode.Dawntrail; + + public override readonly string ToString() + => $"[ColorRow Diffuse={Diffuse} Specular={Specular} Emissive={Emissive} SpecularStrength={SpecularStrength} GlossStrength={GlossStrength} Roughness={Roughness} Metalness={Metalness} Sheen={Sheen} SheenTint={SheenTint} SheenAperture={SheenAperture}]"; } internal static class ColorTableRowExtensions @@ -103,19 +189,50 @@ internal static class ColorTableRowExtensions internal static Half LegacyGloss(this in ColorTableRow row) => row[3]; + internal static Half DawntrailSheen(this in ColorTableRow row) + => row[12]; + + internal static Half DawntrailSheenTint(this in ColorTableRow row) + => row[13]; + + internal static Half DawntrailSheenAperture(this in ColorTableRow row) + => row[14]; + + internal static Half DawntrailRoughness(this in ColorTableRow row) + => row[16]; + + internal static Half DawntrailMetalness(this in ColorTableRow row) + => row[18]; + internal static ref Half LegacySpecularStrengthWrite(this ref ColorTableRow row) => ref row[7]; internal static ref Half LegacyGlossWrite(this ref ColorTableRow row) => ref row[3]; + + internal static ref Half DawntrailSheenWrite(this ref ColorTableRow row) + => ref row[12]; + + internal static ref Half DawntrailSheenTintWrite(this ref ColorTableRow row) + => ref row[13]; + + internal static ref Half DawntrailSheenApertureWrite(this ref ColorTableRow row) + => ref row[14]; + + internal static ref Half DawntrailRoughnessWrite(this ref ColorTableRow row) + => ref row[16]; + + internal static ref Half DawntrailMetalnessWrite(this ref ColorTableRow row) + => ref row[18]; } [JsonConverter(typeof(Converter))] -public struct MaterialValueDesign(ColorRow value, bool enabled, bool revert) +public struct MaterialValueDesign(ColorRow value, bool enabled, bool revert, ColorRow.Mode mode) { - public ColorRow Value = value; - public bool Enabled = enabled; - public bool Revert = revert; + public ColorRow Value = value; + public bool Enabled = enabled; + public bool Revert = revert; + public ColorRow.Mode Mode = mode; public readonly bool Apply(ref MaterialValueState state) { @@ -145,6 +262,8 @@ public struct MaterialValueDesign(ColorRow value, bool enabled, bool revert) writer.WriteStartObject(); writer.WritePropertyName("Revert"); writer.WriteValue(value.Revert); + writer.WritePropertyName("Mode"); + writer.WriteValue(value.Mode.ToString()); writer.WritePropertyName("DiffuseR"); writer.WriteValue(value.Value.Diffuse.X); writer.WritePropertyName("DiffuseG"); @@ -157,16 +276,54 @@ public struct MaterialValueDesign(ColorRow value, bool enabled, bool revert) writer.WriteValue(value.Value.Specular.Y); writer.WritePropertyName("SpecularB"); writer.WriteValue(value.Value.Specular.Z); - writer.WritePropertyName("SpecularA"); - writer.WriteValue(value.Value.SpecularStrength); + if (!float.IsNaN(value.Value.SpecularStrength)) + { + writer.WritePropertyName("SpecularA"); + writer.WriteValue(value.Value.SpecularStrength); + } + writer.WritePropertyName("EmissiveR"); writer.WriteValue(value.Value.Emissive.X); writer.WritePropertyName("EmissiveG"); writer.WriteValue(value.Value.Emissive.Y); writer.WritePropertyName("EmissiveB"); writer.WriteValue(value.Value.Emissive.Z); - writer.WritePropertyName("Gloss"); - writer.WriteValue(value.Value.GlossStrength); + if (!float.IsNaN(value.Value.GlossStrength)) + { + writer.WritePropertyName("Gloss"); + writer.WriteValue(value.Value.GlossStrength); + } + + if (!float.IsNaN(value.Value.Roughness)) + { + writer.WritePropertyName("Roughness"); + writer.WriteValue(value.Value.Roughness); + } + + if (!float.IsNaN(value.Value.Metalness)) + { + writer.WritePropertyName("Metalness"); + writer.WriteValue(value.Value.Metalness); + } + + if (!float.IsNaN(value.Value.Sheen)) + { + writer.WritePropertyName("Sheen"); + writer.WriteValue(value.Value.Sheen); + } + + if (!float.IsNaN(value.Value.SheenTint)) + { + writer.WritePropertyName("SheenTint"); + writer.WriteValue(value.Value.SheenTint); + } + + if (!float.IsNaN(value.Value.SheenAperture)) + { + writer.WritePropertyName("SheenAperture"); + writer.WriteValue(value.Value.SheenAperture); + } + writer.WritePropertyName("Enabled"); writer.WriteValue(value.Enabled); writer.WriteEndObject(); @@ -177,19 +334,26 @@ public struct MaterialValueDesign(ColorRow value, bool enabled, bool revert) JsonSerializer serializer) { var obj = JObject.Load(reader); - Set(ref existingValue.Revert, obj["Revert"]?.Value()); - Set(ref existingValue.Value.Diffuse.X, obj["DiffuseR"]?.Value()); - Set(ref existingValue.Value.Diffuse.Y, obj["DiffuseG"]?.Value()); - Set(ref existingValue.Value.Diffuse.Z, obj["DiffuseB"]?.Value()); - Set(ref existingValue.Value.Specular.X, obj["SpecularR"]?.Value()); - Set(ref existingValue.Value.Specular.Y, obj["SpecularG"]?.Value()); - Set(ref existingValue.Value.Specular.Z, obj["SpecularB"]?.Value()); - Set(ref existingValue.Value.SpecularStrength, obj["SpecularA"]?.Value()); - Set(ref existingValue.Value.Emissive.X, obj["EmissiveR"]?.Value()); - Set(ref existingValue.Value.Emissive.Y, obj["EmissiveG"]?.Value()); - Set(ref existingValue.Value.Emissive.Z, obj["EmissiveB"]?.Value()); - Set(ref existingValue.Value.GlossStrength, obj["Gloss"]?.Value()); - existingValue.Enabled = obj["Enabled"]?.Value() ?? false; + if (obj["Mode"]?.Value() is { } mode) + Enum.TryParse(mode, true, out existingValue.Mode); + Set(ref existingValue.Revert, obj["Revert"]?.Value()); + Set(ref existingValue.Value.Diffuse.X, obj["DiffuseR"]?.Value()); + Set(ref existingValue.Value.Diffuse.Y, obj["DiffuseG"]?.Value()); + Set(ref existingValue.Value.Diffuse.Z, obj["DiffuseB"]?.Value()); + Set(ref existingValue.Value.Specular.X, obj["SpecularR"]?.Value()); + Set(ref existingValue.Value.Specular.Y, obj["SpecularG"]?.Value()); + Set(ref existingValue.Value.Specular.Z, obj["SpecularB"]?.Value()); + Set(ref existingValue.Value.Emissive.X, obj["EmissiveR"]?.Value()); + Set(ref existingValue.Value.Emissive.Y, obj["EmissiveG"]?.Value()); + Set(ref existingValue.Value.Emissive.Z, obj["EmissiveB"]?.Value()); + existingValue.Value.SpecularStrength = obj["SpecularA"]?.Value() ?? float.NaN; + existingValue.Value.GlossStrength = obj["Gloss"]?.Value() ?? float.NaN; + existingValue.Value.Roughness = obj["Roughness"]?.Value() ?? float.NaN; + existingValue.Value.Metalness = obj["Metalness"]?.Value() ?? float.NaN; + existingValue.Value.Sheen = obj["Sheen"]?.Value() ?? float.NaN; + existingValue.Value.SheenTint = obj["SheenTint"]?.Value() ?? float.NaN; + existingValue.Value.SheenAperture = obj["SheenAperture"]?.Value() ?? float.NaN; + existingValue.Enabled = obj["Enabled"]?.Value() ?? false; return existingValue; static void Set(ref T target, T? value) @@ -222,10 +386,13 @@ public struct MaterialValueState( && DrawData.Weapon == rhsData.Weapon && DrawData.Variant == rhsData.Variant && DrawData.Stains == rhsData.Stains - && Game.NearEqual(rhsRow); + && rhsRow.NearEqual(Game, true); public readonly MaterialValueDesign Convert() - => new(Model, true, false); + => new(Model, true, false, Model.GuessMode()); + + public readonly MaterialValueState MergeOnto(in ColorRow previous) + => new(Game, Model.MergeOnto(previous), DrawData, Source); } public readonly struct MaterialValueManager diff --git a/Glamourer/State/InternalStateEditor.cs b/Glamourer/State/InternalStateEditor.cs index eb3403d..1c41769 100644 --- a/Glamourer/State/InternalStateEditor.cs +++ b/Glamourer/State/InternalStateEditor.cs @@ -253,7 +253,7 @@ public class InternalStateEditor( } // Update if edited. - state.Materials.UpdateValue(index, newValue, out _); + state.Materials.UpdateValue(index, newValue.MergeOnto(old.Model), out _); return true; } diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index cbce7d5..ad77853 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -231,7 +231,7 @@ public class StateEditor( Glamourer.Log.Verbose( $"Set material value in state {state.Identifier.Incognito(null)} from {oldValue} to {newValue.Game}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.MaterialValue, settings.Source, state, actors, - new MaterialTransaction(index, oldValue, newValue.Game)); + new MaterialTransaction(index, oldValue, newValue.Game, null, null)); } public void ResetMaterialValue(object data, MaterialValueIndex index, ApplySettings settings) @@ -243,7 +243,8 @@ public class StateEditor( var actors = Applier.ChangeMaterialValue(state, index, true); Glamourer.Log.Verbose( $"Reset material value in state {state.Identifier.Incognito(null)} to game value. [Affecting {actors.ToLazyString("nothing")}.]"); - StateChanged.Invoke(StateChangeType.MaterialValue, settings.Source, state, actors, new MaterialTransaction(index, null, null)); + StateChanged.Invoke(StateChangeType.MaterialValue, settings.Source, state, actors, + new MaterialTransaction(index, null, null, null, null)); } ///